Spaces:
Running
Running
| """ | |
| LifeFlow AI - v11 English + Compact Layout | |
| ✅ English version | |
| ✅ Compact layout (horizontal on widescreen) | |
| ✅ Responsive design | |
| ✅ Optimized for HuggingFace Spaces | |
| """ | |
| import gradio as gr | |
| import plotly.graph_objects as go | |
| from datetime import datetime, time | |
| import json | |
| from typing import Dict, List, Optional, Tuple | |
| import time as time_module | |
| class LifeFlowInteractive: | |
| """LifeFlow AI Interactive Demo v11""" | |
| def __init__(self): | |
| self.team = None | |
| self.current_step = 0 | |
| self.task_list = [] | |
| self.poi_results = [] | |
| self.chat_history = [] | |
| self.reasoning_messages = [] | |
| self.planning_completed = False | |
| self.agent_raw_output = "" | |
| # Settings | |
| self.settings = { | |
| 'google_maps_api_key': '', | |
| 'openweather_api_key': '', | |
| 'anthropic_api_key': '', | |
| 'model': 'claude-sonnet-4-20250514' | |
| } | |
| # Agent info | |
| self.agents_info = { | |
| "planner": { | |
| "name": "Planner", | |
| "avatar": "👔", | |
| "role": "Chief Planning Officer", | |
| "color": "#4A90E2" | |
| }, | |
| "scout": { | |
| "name": "Scout", | |
| "avatar": "🕵️", | |
| "role": "Location Scout Expert", | |
| "color": "#50C878" | |
| }, | |
| "optimizer": { | |
| "name": "Optimizer", | |
| "avatar": "🤖", | |
| "role": "AI Optimization Engine", | |
| "color": "#F5A623" | |
| }, | |
| "validator": { | |
| "name": "Validator", | |
| "avatar": "🛡️", | |
| "role": "Quality Assurance Expert", | |
| "color": "#7ED321" | |
| }, | |
| "weather": { | |
| "name": "Weather", | |
| "avatar": "🌈", | |
| "role": "Weather Analyst", | |
| "color": "#50E3C2" | |
| }, | |
| "traffic": { | |
| "name": "Traffic", | |
| "avatar": "🚗", | |
| "role": "Traffic Planner", | |
| "color": "#FF6B6B" | |
| } | |
| } | |
| def create_agent_card(self, agent_key: str, status: str = "idle", message: str = ""): | |
| """Create Agent card (compact version)""" | |
| agent = self.agents_info[agent_key] | |
| status_icons = { | |
| "idle": "⚪", | |
| "thinking": "💭", | |
| "using_tool": "🔧", | |
| "working": "⚙️", | |
| "completed": "✅", | |
| "waiting": "⏸️", | |
| "error": "❌" | |
| } | |
| icon = status_icons.get(status, "⚪") | |
| breathing_class = "breathing" if status in ["working", "using_tool"] else "" | |
| return f""" | |
| <div class="{breathing_class}" style=" | |
| background: var(--background-fill-secondary); | |
| border-left: 3px solid {agent['color']}; | |
| border-radius: 6px; | |
| padding: 10px; | |
| margin: 6px 0; | |
| "> | |
| <div style="display: flex; align-items: center; gap: 8px;"> | |
| <div style="font-size: 24px;">{agent['avatar']}</div> | |
| <div style="flex-grow: 1;"> | |
| <div style="display: flex; justify-content: space-between; align-items: center;"> | |
| <strong style="color: var(--body-text-color); font-size: 14px;"> | |
| {agent['name']} | |
| </strong> | |
| <span style="font-size: 16px;">{icon}</span> | |
| </div> | |
| {f'<div style="color: var(--body-text-color); opacity: 0.7; font-size: 12px; margin-top: 4px;">{message}</div>' if message else ''} | |
| </div> | |
| </div> | |
| </div> | |
| """ | |
| def add_reasoning_message(self, agent: str, message: str, msg_type: str = "info"): | |
| """Add reasoning message""" | |
| icon_map = { | |
| "thinking": "💭", | |
| "tool": "🔧", | |
| "success": "✅", | |
| "info": "ℹ️", | |
| "warning": "⚠️", | |
| "error": "❌" | |
| } | |
| icon = icon_map.get(msg_type, "ℹ️") | |
| agent_name = self.agents_info.get(agent, {}).get("name", agent) | |
| self.reasoning_messages.append({ | |
| "agent": agent_name, | |
| "message": message, | |
| "icon": icon, | |
| "timestamp": datetime.now().strftime("%H:%M:%S") | |
| }) | |
| def get_reasoning_html(self) -> str: | |
| """Generate reasoning HTML (compact scrollable)""" | |
| if not self.reasoning_messages: | |
| return """ | |
| <div style=" | |
| padding: 15px; | |
| text-align: center; | |
| color: var(--body-text-color); | |
| opacity: 0.6; | |
| background: var(--background-fill-secondary); | |
| border-radius: 6px; | |
| "> | |
| 💬 AI conversation log will appear here | |
| </div> | |
| """ | |
| messages_html = "" | |
| for msg in self.reasoning_messages: | |
| messages_html += f""" | |
| <div style=" | |
| padding: 8px; | |
| margin: 6px 0; | |
| background: var(--background-fill-secondary); | |
| border-radius: 6px; | |
| border-left: 2px solid #4A90E2; | |
| "> | |
| <div style="display: flex; align-items: center; gap: 6px; margin-bottom: 4px;"> | |
| <span style="font-size: 14px;">{msg['icon']}</span> | |
| <strong style="color: var(--body-text-color); font-size: 13px;">{msg['agent']}</strong> | |
| <span style="color: var(--body-text-color); opacity: 0.5; font-size: 11px; margin-left: auto;"> | |
| {msg['timestamp']} | |
| </span> | |
| </div> | |
| <div style="color: var(--body-text-color); opacity: 0.85; padding-left: 20px; font-size: 12px;"> | |
| {msg['message']} | |
| </div> | |
| </div> | |
| """ | |
| return f""" | |
| <div style=" | |
| max-height: 400px; | |
| overflow-y: auto; | |
| padding: 10px; | |
| background: var(--background-fill-primary); | |
| border-radius: 6px; | |
| border: 1px solid var(--border-color-primary); | |
| "> | |
| {messages_html} | |
| </div> | |
| """ | |
| def create_task_list_ui(self, tasks: List[Dict]) -> str: | |
| """Create task list UI (compact)""" | |
| if not tasks: | |
| return "<div style='color: var(--body-text-color);'>No tasks yet</div>" | |
| priority_colors = { | |
| "HIGH": "#FF6B6B", | |
| "MEDIUM": "#F5A623", | |
| "LOW": "#7ED321" | |
| } | |
| category_icons = { | |
| "medical": "🏥", | |
| "shopping": "🛒", | |
| "postal": "📮", | |
| "dining": "🍽️", | |
| "financial": "🏦", | |
| "other": "📋" | |
| } | |
| html = "<div style='display: flex; flex-direction: column; gap: 8px;'>" | |
| for i, task in enumerate(tasks, 1): | |
| priority = task.get('priority', 'MEDIUM') | |
| category = task.get('category', 'other') | |
| icon = category_icons.get(category, "📋") | |
| color = priority_colors.get(priority, "#7ED321") | |
| time_window = task.get('time_window', 'Anytime') | |
| html += f""" | |
| <div style=" | |
| background: var(--background-fill-secondary); | |
| border-left: 3px solid {color}; | |
| border-radius: 6px; | |
| padding: 10px; | |
| "> | |
| <div style="display: flex; align-items: center; gap: 6px; margin-bottom: 4px;"> | |
| <span style="font-size: 20px;">{icon}</span> | |
| <strong style="color: var(--body-text-color); font-size: 13px;"> | |
| Task {i}: {task['description']} | |
| </strong> | |
| </div> | |
| <div style="color: var(--body-text-color); opacity: 0.7; font-size: 11px;"> | |
| <span style="background: {color}; color: white; padding: 2px 6px; border-radius: 3px; margin-right: 6px;"> | |
| {priority} | |
| </span> | |
| <span>⏰ {time_window}</span> | |
| </div> | |
| </div> | |
| """ | |
| html += "</div>" | |
| return html | |
| def create_task_summary(self, tasks: List[Dict]) -> str: | |
| """Create task summary (compact)""" | |
| high_count = sum(1 for t in tasks if t.get('priority') == 'HIGH') | |
| medium_count = sum(1 for t in tasks if t.get('priority') == 'MEDIUM') | |
| low_count = sum(1 for t in tasks if t.get('priority') == 'LOW') | |
| return f""" | |
| <div style=" | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| padding: 15px; | |
| border-radius: 6px; | |
| color: white; | |
| margin-top: 10px; | |
| "> | |
| <h4 style="margin: 0 0 10px 0; font-size: 14px;">📊 Task Summary</h4> | |
| <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px;"> | |
| <div style="text-align: center;"> | |
| <div style="font-size: 24px; font-weight: bold;">{len(tasks)}</div> | |
| <div style="opacity: 0.9; font-size: 11px;">Total</div> | |
| </div> | |
| <div style="text-align: center;"> | |
| <div style="font-size: 24px; font-weight: bold;">{high_count}</div> | |
| <div style="opacity: 0.9; font-size: 11px;">High Priority</div> | |
| </div> | |
| <div style="text-align: center;"> | |
| <div style="font-size: 24px; font-weight: bold;">{medium_count + low_count}</div> | |
| <div style="opacity: 0.9; font-size: 11px;">Med/Low</div> | |
| </div> | |
| </div> | |
| </div> | |
| """ | |
| def create_trip_result_summary(self, result: Dict) -> str: | |
| """Create trip result summary (compact, left side)""" | |
| stops = result.get('route', {}).get('stops', []) | |
| return f""" | |
| <div style=" | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| padding: 20px; | |
| border-radius: 8px; | |
| color: white; | |
| box-shadow: 0 4px 12px rgba(0,0,0,0.15); | |
| "> | |
| <div style="display: flex; align-items: center; gap: 10px; margin-bottom: 15px;"> | |
| <span style="font-size: 36px;">🎉</span> | |
| <div> | |
| <h3 style="margin: 0; font-size: 18px;">Trip Planning Complete!</h3> | |
| <p style="margin: 5px 0 0 0; opacity: 0.9; font-size: 12px;">Optimized {len(stops)} stops</p> | |
| </div> | |
| </div> | |
| <div style="background: rgba(255,255,255,0.15); padding: 12px; border-radius: 6px; margin-bottom: 12px;"> | |
| <h4 style="margin: 0 0 8px 0; font-size: 13px;">⏰ Schedule</h4> | |
| <div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px; font-size: 11px;"> | |
| <div>• Start: <strong>08:30</strong></div> | |
| <div>• Finish: <strong>11:25</strong> ✅</div> | |
| <div>• Deadline: <strong>15:00</strong></div> | |
| <div>• Buffer: <strong>3h 35m</strong> 🎯</div> | |
| </div> | |
| </div> | |
| <div style="background: rgba(255,255,255,0.15); padding: 12px; border-radius: 6px;"> | |
| <h4 style="margin: 0 0 8px 0; font-size: 13px;">📍 Route</h4> | |
| <div style="font-size: 11px; line-height: 1.6;"> | |
| {' → '.join([stop['name'] for stop in stops])} | |
| </div> | |
| </div> | |
| <div style="margin-top: 12px; padding: 10px; background: rgba(255,255,255,0.1); border-radius: 6px; font-size: 11px;"> | |
| 💡 Check the <strong>Full Report</strong> tab for detailed analysis | |
| </div> | |
| </div> | |
| """ | |
| def create_agent_text_output(self, result: Dict) -> str: | |
| """Create Agent text output (for right side tab)""" | |
| stops = result.get('route', {}).get('stops', []) | |
| output = "=" * 80 + "\n" | |
| output += "LIFEFLOW AI - COMPLETE TRIP PLANNING REPORT\n" | |
| output += "=" * 80 + "\n\n" | |
| output += "【PLANNING SUMMARY】\n" | |
| output += f"Total Stops: {len(stops)}\n" | |
| output += f"Start Time: 08:30\n" | |
| output += f"Estimated Completion: 11:25\n" | |
| output += f"Deadline: 15:00\n" | |
| output += f"Buffer Time: 3h 35m\n\n" | |
| output += "=" * 80 + "\n" | |
| output += "【PLANNER AGENT ANALYSIS】\n" | |
| output += "=" * 80 + "\n" | |
| output += "✓ Identified 3 tasks\n" | |
| output += "✓ Task 1: Visit hospital (Medical - HIGH)\n" | |
| output += " - Time window: 08:00-12:00\n" | |
| output += " - Duration: 45 minutes\n" | |
| output += "✓ Task 2: Buy groceries (Shopping - MEDIUM)\n" | |
| output += " - Time window: Anytime\n" | |
| output += " - Duration: 30 minutes\n" | |
| output += "✓ Task 3: Mail package (Postal - HIGH)\n" | |
| output += " - Time window: 09:00-15:00\n" | |
| output += " - Duration: 20 minutes\n\n" | |
| output += "【TIME CONSTRAINT ANALYSIS】\n" | |
| output += "- Hospital time window is tight, must prioritize\n" | |
| output += "- Post office deadline is 15:00, must complete on time\n" | |
| output += "- Supermarket time is flexible, can be used as buffer\n\n" | |
| output += "=" * 80 + "\n" | |
| output += "【SCOUT AGENT SEARCH RESULTS】\n" | |
| output += "=" * 80 + "\n" | |
| output += "✓ POI search complete\n\n" | |
| for i, stop in enumerate(stops, 1): | |
| output += f"Stop {i}: {stop['name']}\n" | |
| output += f" Address: {stop['address']}\n" | |
| output += f" Phone: {stop['phone']}\n" | |
| output += f" Rating: {stop['rating']} ⭐\n" | |
| output += f" Distance: {'Start' if i == 1 else f'{0.5 * i}km'}\n\n" | |
| output += "【DISTANCE MATRIX】\n" | |
| output += "- Start → NTU Hospital: 0.8km (15 min)\n" | |
| output += "- Hospital → PX Mart: 0.6km (12 min)\n" | |
| output += "- PX Mart → Post Office: 0.8km (15 min)\n\n" | |
| output += "=" * 80 + "\n" | |
| output += "【OPTIMIZER AGENT ROUTE OPTIMIZATION】\n" | |
| output += "=" * 80 + "\n" | |
| output += "✓ Using OR-Tools TSPTW solver\n\n" | |
| output += "【OPTIMIZATION PROCESS】\n" | |
| output += "Phase 1: Greedy Construction\n" | |
| output += " - Initial route: Hospital → Supermarket → Post Office\n" | |
| output += " - Total distance: 2.2km\n" | |
| output += " - Total time: 95 minutes\n\n" | |
| output += "Phase 2: 2-opt Improvement\n" | |
| output += " - Attempting order swap...\n" | |
| output += " - Current order is optimal\n" | |
| output += " - No improvement needed\n\n" | |
| output += "Phase 3: Time Window Validation\n" | |
| output += " - Hospital: 09:00 ∈ [08:00, 12:00] ✓\n" | |
| output += " - Supermarket: 10:15 ∈ [Anytime] ✓\n" | |
| output += " - Post Office: 11:05 ∈ [09:00, 15:00] ✓\n\n" | |
| output += "【OPTIMAL ROUTE】\n" | |
| output += "Stop Order: Hospital → Supermarket → Post Office\n" | |
| output += "Total Distance: 2.2km\n" | |
| output += "Total Time: 95 minutes (travel + stops)\n" | |
| output += "Completion: 11:25\n" | |
| output += "Deadline Met: ✓ (3h 35m early)\n\n" | |
| output += "=" * 80 + "\n" | |
| output += "【VALIDATOR AGENT FEASIBILITY CHECK】\n" | |
| output += "=" * 80 + "\n" | |
| output += "✓ Comprehensive feasibility check passed\n\n" | |
| output += "【CHECK ITEMS】\n" | |
| output += "✓ Time window constraints: All satisfied\n" | |
| output += "✓ Deadline constraint: Met (11:25 < 15:00)\n" | |
| output += "✓ Priority handling: All HIGH tasks completed\n" | |
| output += "✓ Business hours: All POIs open during visit\n" | |
| output += "✓ Route continuity: No backtracking\n\n" | |
| output += "【RISK ASSESSMENT】\n" | |
| output += "- Risk Level: LOW\n" | |
| output += "- Sufficient buffer for unexpected delays\n" | |
| output += "- Hospital may have wait time, buffer allocated\n\n" | |
| output += "【SCORE】\n" | |
| output += "Overall Score: 98/100 (Excellent)\n" | |
| output += " - Time Efficiency: 95/100\n" | |
| output += " - Route Optimization: 100/100\n" | |
| output += " - Constraint Satisfaction: 100/100\n" | |
| output += " - Practicality: 95/100\n\n" | |
| output += "=" * 80 + "\n" | |
| output += "【WEATHER AGENT ANALYSIS】\n" | |
| output += "=" * 80 + "\n" | |
| output += "✓ Weather data obtained\n\n" | |
| output += "【CURRENT WEATHER】\n" | |
| output += "- Condition: Sunny ☀️\n" | |
| output += "- Temperature: 23°C\n" | |
| output += "- Precipitation: 10%\n" | |
| output += "- Wind: 5 m/s\n\n" | |
| output += "【WEATHER IMPACT】\n" | |
| output += "- Impact on trip: None\n" | |
| output += "- Recommendation: Good weather for travel\n\n" | |
| output += "=" * 80 + "\n" | |
| output += "【TRAFFIC AGENT ANALYSIS】\n" | |
| output += "=" * 80 + "\n" | |
| output += "✓ Traffic data analyzed\n\n" | |
| output += "【TRAFFIC FORECAST】\n" | |
| output += "- 08:30-09:00: Light congestion (morning rush)\n" | |
| output += "- 09:00-11:00: Good conditions\n" | |
| output += "- 11:00-11:30: Good conditions\n\n" | |
| output += "【RECOMMENDED DEPARTURE】\n" | |
| output += "- Recommended: 08:30 (as planned)\n" | |
| output += "- Alternative: 08:15 (avoid some congestion)\n\n" | |
| output += "【ESTIMATED TRAVEL TIME】\n" | |
| output += "- Start → Hospital: 18 min (with congestion)\n" | |
| output += "- Hospital → Supermarket: 12 min\n" | |
| output += "- Supermarket → Post Office: 15 min\n" | |
| output += "- Total Travel: 45 minutes\n\n" | |
| output += "=" * 80 + "\n" | |
| output += "【TEAM MODEL FINAL DECISION】\n" | |
| output += "=" * 80 + "\n" | |
| output += "✓ All agent reports synthesized\n\n" | |
| output += "【DECISION RATIONALE】\n" | |
| output += "1. Planner identified 3 tasks with clear time constraints\n" | |
| output += "2. Scout found high-quality POIs (all rated 4.3+)\n" | |
| output += "3. Optimizer confirmed current order is optimal\n" | |
| output += "4. Validator verified all constraints satisfied\n" | |
| output += "5. Weather shows good conditions, no adjustments\n" | |
| output += "6. Traffic shows acceptable conditions, proceed as planned\n\n" | |
| output += "【FINAL PLAN】\n" | |
| output += "Adopt Optimizer's route order\n" | |
| output += "Recommend departure at 08:30\n" | |
| output += "Expected completion at 11:25\n" | |
| output += "Safety margin: 3h 35m\n\n" | |
| output += "【RECOMMENDATIONS】\n" | |
| output += "1. Depart at 08:30 to ensure completion of all tasks\n" | |
| output += "2. Hospital visit may take longer, buffer allocated\n" | |
| output += "3. After post office, can have lunch nearby\n" | |
| output += "4. If delayed, can skip supermarket (MEDIUM priority)\n\n" | |
| output += "=" * 80 + "\n" | |
| output += "Planning completed: " + datetime.now().strftime("%Y-%m-%d %H:%M:%S") + "\n" | |
| output += "=" * 80 + "\n" | |
| return output | |
| def create_map(self, result: Optional[Dict] = None) -> go.Figure: | |
| """Create map""" | |
| if not result: | |
| fig = go.Figure() | |
| fig.update_layout( | |
| title="Map will appear after planning", | |
| height=500, | |
| template="plotly_dark" | |
| ) | |
| return fig | |
| stops = [ | |
| {"name": "Start", "lat": 25.0330, "lng": 121.5654}, | |
| {"name": "NTU Hospital", "lat": 25.0408, "lng": 121.5318}, | |
| {"name": "PX Mart", "lat": 25.0428, "lng": 121.5298}, | |
| {"name": "Post Office", "lat": 25.0468, "lng": 121.5358} | |
| ] | |
| fig = go.Figure() | |
| lats = [stop['lat'] for stop in stops] | |
| lngs = [stop['lng'] for stop in stops] | |
| fig.add_trace(go.Scattermapbox( | |
| lat=lats, | |
| lon=lngs, | |
| mode='lines+markers', | |
| marker=dict(size=15, color='red'), | |
| line=dict(width=3, color='blue'), | |
| text=[stop['name'] for stop in stops], | |
| hoverinfo='text' | |
| )) | |
| fig.update_layout( | |
| mapbox=dict( | |
| style='open-street-map', | |
| center=dict(lat=25.04, lon=121.53), | |
| zoom=13 | |
| ), | |
| height=500, | |
| margin=dict(l=0, r=0, t=0, b=0) | |
| ) | |
| return fig | |
| def step1_analyze_tasks(self, user_input: str): | |
| """Step 1: Analyze tasks""" | |
| self.reasoning_messages = [] | |
| self.add_reasoning_message("planner", "Analyzing user requirements...", "thinking") | |
| time_module.sleep(0.5) | |
| self.add_reasoning_message("planner", "Using tool: parse_requirements()", "tool") | |
| time_module.sleep(0.5) | |
| self.add_reasoning_message("planner", "✅ Identified 3 tasks: hospital, supermarket, post office", "success") | |
| self.add_reasoning_message("planner", "Using tool: extract_time_windows()", "tool") | |
| time_module.sleep(0.5) | |
| self.add_reasoning_message("planner", "✅ Time windows extracted", "success") | |
| self.task_list = [ | |
| { | |
| "description": "Visit NTU Hospital", | |
| "category": "medical", | |
| "priority": "HIGH", | |
| "time_window": "08:00-12:00" | |
| }, | |
| { | |
| "description": "Buy groceries at PX Mart", | |
| "category": "shopping", | |
| "priority": "MEDIUM", | |
| "time_window": "Anytime" | |
| }, | |
| { | |
| "description": "Mail package at post office", | |
| "category": "postal", | |
| "priority": "HIGH", | |
| "time_window": "09:00-15:00" | |
| } | |
| ] | |
| agent_updates = [ | |
| self.create_agent_card("planner", "completed", "✅ Analysis complete"), | |
| self.create_agent_card("scout", "idle", "On standby"), | |
| self.create_agent_card("optimizer", "idle", "On standby"), | |
| self.create_agent_card("validator", "idle", "On standby"), | |
| self.create_agent_card("weather", "idle", "On standby"), | |
| self.create_agent_card("traffic", "idle", "On standby") | |
| ] | |
| task_list_ui = self.create_task_list_ui(self.task_list) | |
| task_summary = self.create_task_summary(self.task_list) | |
| return ( | |
| *agent_updates, | |
| self.get_reasoning_html(), | |
| task_list_ui, | |
| task_summary, | |
| gr.update(visible=True), | |
| gr.update(visible=True), | |
| [], | |
| "✅ Task analysis complete" | |
| ) | |
| def step2_search_pois(self): | |
| """Step 2: Search POIs""" | |
| self.add_reasoning_message("scout", "Starting POI search...", "thinking") | |
| time_module.sleep(0.5) | |
| agent_updates = [ | |
| self.create_agent_card("planner", "completed", "✅ Complete"), | |
| self.create_agent_card("scout", "working", "🔧 Searching POIs..."), | |
| self.create_agent_card("optimizer", "waiting", "⏸️ Waiting"), | |
| self.create_agent_card("validator", "waiting", "⏸️ Waiting"), | |
| self.create_agent_card("weather", "waiting", "⏸️ Waiting"), | |
| self.create_agent_card("traffic", "waiting", "⏸️ Waiting") | |
| ] | |
| yield (*agent_updates, self.get_reasoning_html(), "", gr.update(), gr.update(), gr.update(), gr.update(), | |
| "🔍 Searching locations...") | |
| self.add_reasoning_message("scout", "Using tool: search_poi(query='hospital')", "tool") | |
| time_module.sleep(0.8) | |
| self.add_reasoning_message("scout", "✅ Found NTU Hospital (0.8km, 4.8★)", "success") | |
| self.add_reasoning_message("scout", "Using tool: search_poi(query='supermarket')", "tool") | |
| time_module.sleep(0.8) | |
| self.add_reasoning_message("scout", "✅ Found PX Mart (1.2km, 24/7)", "success") | |
| self.add_reasoning_message("scout", "Using tool: search_poi(query='post office')", "tool") | |
| time_module.sleep(0.8) | |
| self.add_reasoning_message("scout", "✅ Found Post Office (1.8km, until 17:30)", "success") | |
| self.add_reasoning_message("scout", "Using tool: calculate_distance_matrix()", "tool") | |
| time_module.sleep(0.5) | |
| self.add_reasoning_message("scout", "✅ Distance matrix calculated", "success") | |
| agent_updates = [ | |
| self.create_agent_card("planner", "completed", "✅ Complete"), | |
| self.create_agent_card("scout", "completed", "✅ POI search complete"), | |
| self.create_agent_card("optimizer", "waiting", "⏸️ Waiting"), | |
| self.create_agent_card("validator", "waiting", "⏸️ Waiting"), | |
| self.create_agent_card("weather", "waiting", "⏸️ Waiting"), | |
| self.create_agent_card("traffic", "waiting", "⏸️ Waiting") | |
| ] | |
| yield (*agent_updates, self.get_reasoning_html(), "", gr.update(), gr.update(), gr.update(), gr.update(), | |
| "✅ POI search complete") | |
| def step3_optimize_route(self): | |
| """Step 3: Optimize route""" | |
| self.add_reasoning_message("optimizer", "Starting route optimization...", "thinking") | |
| time_module.sleep(0.5) | |
| agent_updates = [ | |
| self.create_agent_card("planner", "completed", "✅ Complete"), | |
| self.create_agent_card("scout", "completed", "✅ Complete"), | |
| self.create_agent_card("optimizer", "working", "⚙️ Optimizing..."), | |
| self.create_agent_card("validator", "waiting", "⏸️ Waiting"), | |
| self.create_agent_card("weather", "waiting", "⏸️ Waiting"), | |
| self.create_agent_card("traffic", "waiting", "⏸️ Waiting") | |
| ] | |
| yield (*agent_updates, self.get_reasoning_html(), "", gr.update(), gr.update(), gr.update(), gr.update(), | |
| "⚙️ Optimizing route...") | |
| self.add_reasoning_message("optimizer", "Using tool: optimize_with_ortools()", "tool") | |
| time_module.sleep(1.0) | |
| self.add_reasoning_message("optimizer", "✅ TSPTW solved, improved 15%", "success") | |
| self.add_reasoning_message("validator", "Validating feasibility...", "thinking") | |
| time_module.sleep(0.5) | |
| agent_updates = [ | |
| self.create_agent_card("planner", "completed", "✅ Complete"), | |
| self.create_agent_card("scout", "completed", "✅ Complete"), | |
| self.create_agent_card("optimizer", "completed", "✅ Optimized"), | |
| self.create_agent_card("validator", "working", "🛡️ Validating..."), | |
| self.create_agent_card("weather", "waiting", "⏸️ Waiting"), | |
| self.create_agent_card("traffic", "waiting", "⏸️ Waiting") | |
| ] | |
| yield (*agent_updates, self.get_reasoning_html(), "", gr.update(), gr.update(), gr.update(), gr.update(), | |
| "🛡️ Validating...") | |
| self.add_reasoning_message("validator", "Using tool: validate_time_windows()", "tool") | |
| time_module.sleep(0.8) | |
| self.add_reasoning_message("validator", "✅ All time windows satisfied", "success") | |
| self.add_reasoning_message("validator", "Using tool: check_deadline()", "tool") | |
| time_module.sleep(0.8) | |
| self.add_reasoning_message("validator", "✅ Deadline check passed (3h35m buffer)", "success") | |
| self.add_reasoning_message("weather", "Checking weather...", "thinking") | |
| time_module.sleep(0.5) | |
| agent_updates = [ | |
| self.create_agent_card("planner", "completed", "✅ Complete"), | |
| self.create_agent_card("scout", "completed", "✅ Complete"), | |
| self.create_agent_card("optimizer", "completed", "✅ Complete"), | |
| self.create_agent_card("validator", "completed", "✅ Validated"), | |
| self.create_agent_card("weather", "working", "🌈 Checking..."), | |
| self.create_agent_card("traffic", "waiting", "⏸️ Waiting") | |
| ] | |
| yield (*agent_updates, self.get_reasoning_html(), "", gr.update(), gr.update(), gr.update(), gr.update(), | |
| "🌈 Checking weather...") | |
| self.add_reasoning_message("weather", "Using tool: check_weather()", "tool") | |
| time_module.sleep(0.8) | |
| self.add_reasoning_message("weather", "✅ Sunny, 23°C, perfect for travel", "success") | |
| self.add_reasoning_message("traffic", "Checking traffic...", "thinking") | |
| time_module.sleep(0.5) | |
| agent_updates = [ | |
| self.create_agent_card("planner", "completed", "✅ Complete"), | |
| self.create_agent_card("scout", "completed", "✅ Complete"), | |
| self.create_agent_card("optimizer", "completed", "✅ Complete"), | |
| self.create_agent_card("validator", "completed", "✅ Complete"), | |
| self.create_agent_card("weather", "completed", "✅ Good weather"), | |
| self.create_agent_card("traffic", "working", "🚗 Checking...") | |
| ] | |
| yield (*agent_updates, self.get_reasoning_html(), "", gr.update(), gr.update(), gr.update(), gr.update(), | |
| "🚗 Checking traffic...") | |
| self.add_reasoning_message("traffic", "Using tool: check_traffic()", "tool") | |
| time_module.sleep(0.8) | |
| self.add_reasoning_message("traffic", "✅ Good road conditions", "success") | |
| agent_updates = [ | |
| self.create_agent_card("planner", "completed", "✅ Complete"), | |
| self.create_agent_card("scout", "completed", "✅ Complete"), | |
| self.create_agent_card("optimizer", "completed", "✅ Complete"), | |
| self.create_agent_card("validator", "completed", "✅ Complete"), | |
| self.create_agent_card("weather", "completed", "✅ Complete"), | |
| self.create_agent_card("traffic", "completed", "✅ Complete") | |
| ] | |
| result = { | |
| 'route': { | |
| 'stops': [ | |
| {'name': 'NTU Hospital', 'address': 'No. 1, Changde St., Taipei', 'arrival_time': '09:00', | |
| 'duration': '45', | |
| 'rating': '4.8', 'phone': '02-2312-3456'}, | |
| {'name': 'PX Mart', 'address': 'Roosevelt Rd. Sec. 3, Taipei', 'arrival_time': '10:15', | |
| 'duration': '30', 'rating': '4.5', 'phone': '02-2362-7890'}, | |
| {'name': 'Main Post Office', 'address': 'Zhongxiao W. Rd. Sec. 1, Taipei', 'arrival_time': '11:05', | |
| 'duration': '20', 'rating': '4.3', 'phone': '02-2311-1234'} | |
| ] | |
| } | |
| } | |
| trip_result = self.create_trip_result_summary(result) | |
| agent_text = self.create_agent_text_output(result) | |
| map_fig = self.create_map(result) | |
| yield ( | |
| *agent_updates, | |
| self.get_reasoning_html(), | |
| trip_result, | |
| agent_text, | |
| map_fig, | |
| gr.update(visible=True), | |
| gr.update(visible=True), | |
| "🎉 Planning complete!" | |
| ) | |
| def modify_task_chat(self, message: str, history: List): | |
| """Modify tasks via chat""" | |
| if not message: | |
| return history, self.create_task_list_ui(self.task_list) | |
| history.append({"role": "user", "content": message}) | |
| history.append({"role": "assistant", "content": "Noted. I've recorded your request."}) | |
| return history, self.create_task_list_ui(self.task_list) | |
| def save_settings(self, google_key: str, weather_key: str, anthropic_key: str, model: str): | |
| """Save settings""" | |
| self.settings['google_maps_api_key'] = google_key | |
| self.settings['openweather_api_key'] = weather_key | |
| self.settings['anthropic_api_key'] = anthropic_key | |
| self.settings['model'] = model | |
| return "✅ Settings saved" | |
| def build_interface(self): | |
| """Build Gradio interface (compact layout)""" | |
| with gr.Blocks( | |
| theme=gr.themes.Soft( | |
| primary_hue="blue", | |
| secondary_hue="green", | |
| neutral_hue="slate", | |
| font=gr.themes.GoogleFont("Inter"), | |
| font_mono=gr.themes.GoogleFont("IBM Plex Mono") | |
| ), | |
| css=""" | |
| .breathing { | |
| animation: breathing 2s ease-in-out infinite; | |
| } | |
| @keyframes breathing { | |
| 0%, 100% { opacity: 1; transform: scale(1); } | |
| 50% { opacity: 0.9; transform: scale(1.02); } | |
| } | |
| .compact-container { | |
| max-width: 1400px; | |
| margin: 0 auto; | |
| } | |
| """, | |
| title="LifeFlow AI - Intelligent Trip Planning" | |
| ) as demo: | |
| gr.Markdown(""" | |
| # 🚀 LifeFlow AI - Intelligent Trip Planning System | |
| **Multi-Agent AI System** for optimizing daily errands and activities | |
| """) | |
| with gr.Row(elem_classes="compact-container"): | |
| # Left: Input + Tasks + Agents + Results (narrower) | |
| with gr.Column(scale=2, min_width=350): | |
| # Input area | |
| with gr.Group(visible=True) as input_area: | |
| gr.Markdown("### 📝 Describe Your Needs") | |
| user_input = gr.Textbox( | |
| label="Requirements", | |
| placeholder="e.g., Visit hospital tomorrow morning, buy groceries, mail package before 3pm", | |
| lines=3 | |
| ) | |
| with gr.Row(): | |
| start_location = gr.Textbox( | |
| label="Start", | |
| placeholder="Taipei", | |
| scale=2 | |
| ) | |
| start_time = gr.Textbox( | |
| label="Time", | |
| placeholder="08:30", | |
| scale=1 | |
| ) | |
| deadline = gr.Textbox( | |
| label="Deadline (optional)", | |
| placeholder="e.g., 15:00" | |
| ) | |
| analyze_btn = gr.Button("🚀 Start AI Analysis", variant="primary", size="lg") | |
| # Task confirmation | |
| with gr.Group(visible=False) as task_confirm_area: | |
| gr.Markdown("### ✅ Confirm Tasks") | |
| task_list_display = gr.HTML() | |
| task_summary_display = gr.HTML() | |
| with gr.Row(): | |
| back_btn = gr.Button("⬅️ Back", variant="secondary", scale=1) | |
| confirm_btn = gr.Button("✅ Confirm & Plan", variant="primary", scale=2) | |
| # Trip result summary | |
| with gr.Group(visible=False) as trip_result_area: | |
| trip_result_display = gr.HTML() | |
| # AI Team | |
| with gr.Group(visible=False) as team_area: | |
| gr.Markdown("### 🤖 AI Expert Team") | |
| agent_displays = [] | |
| for agent_key in ['planner', 'scout', 'optimizer', 'validator', 'weather', 'traffic']: | |
| agent_card = gr.HTML(value=self.create_agent_card(agent_key, "idle", "On standby")) | |
| agent_displays.append(agent_card) | |
| # Right: Status + Chat + Tabs (wider) | |
| with gr.Column(scale=3, min_width=500): | |
| # Status bar | |
| with gr.Group(): | |
| status_bar = gr.Textbox( | |
| label="📊 Status", | |
| value="Waiting for input...", | |
| interactive=False, | |
| max_lines=1 | |
| ) | |
| # Chat modification | |
| with gr.Group(visible=False) as chat_modify_area: | |
| gr.Markdown("### 💬 Modify Tasks") | |
| chatbot = gr.Chatbot( | |
| type='messages', | |
| value=[], | |
| height=120, | |
| show_label=False | |
| ) | |
| with gr.Row(): | |
| chat_input = gr.Textbox( | |
| placeholder="e.g., Change task 2 to high priority", | |
| show_label=False, | |
| scale=4 | |
| ) | |
| chat_send = gr.Button("Send", variant="primary", scale=1) | |
| # Tabs | |
| with gr.Tabs() as tabs: | |
| # Tab 1: AI Conversation | |
| with gr.Tab("💬 AI Conversation", id="chat_tab"): | |
| reasoning_output = gr.HTML( | |
| value=self.get_reasoning_html() | |
| ) | |
| # Tab 2: Full Report | |
| with gr.Tab("📊 Full Report", id="summary_tab", visible=False) as summary_tab: | |
| gr.Markdown("*Team Agent complete analysis (plain text)*") | |
| complete_summary_output = gr.Textbox( | |
| show_label=False, | |
| lines=20, | |
| max_lines=25, | |
| interactive=False | |
| ) | |
| # Tab 3: Map | |
| with gr.Tab("🗺️ Route Map", id="map_tab", visible=False) as map_tab: | |
| map_output = gr.Plot(value=self.create_map(), show_label=False) | |
| # Tab 4: Settings | |
| with gr.Tab("⚙️ Settings", id="settings_tab"): | |
| gr.Markdown("### 🔑 API Keys") | |
| google_maps_key = gr.Textbox( | |
| label="Google Maps API Key", | |
| placeholder="AIzaSy...", | |
| type="password" | |
| ) | |
| openweather_key = gr.Textbox( | |
| label="OpenWeather API Key", | |
| type="password" | |
| ) | |
| anthropic_key = gr.Textbox( | |
| label="Anthropic API Key", | |
| placeholder="sk-ant-...", | |
| type="password" | |
| ) | |
| gr.Markdown("### 🤖 Model") | |
| model_choice = gr.Dropdown( | |
| label="Select Model", | |
| choices=[ | |
| "claude-sonnet-4-20250514", | |
| "claude-opus-4-20250514", | |
| "gpt-4o", | |
| "gemini-pro" | |
| ], | |
| value="claude-sonnet-4-20250514" | |
| ) | |
| save_settings_btn = gr.Button("💾 Save", variant="primary") | |
| settings_status = gr.Textbox(label="Status", value="", interactive=False) | |
| # Examples | |
| gr.Examples( | |
| examples=[ | |
| ["Visit hospital tomorrow morning, buy groceries, mail package before 3pm", "Taipei", "08:30", | |
| "15:00"] | |
| ], | |
| inputs=[user_input, start_location, start_time, deadline] | |
| ) | |
| # Event bindings | |
| analyze_btn.click( | |
| fn=self.step1_analyze_tasks, | |
| inputs=[user_input], | |
| outputs=[ | |
| *agent_displays, | |
| reasoning_output, | |
| task_list_display, | |
| task_summary_display, | |
| task_confirm_area, | |
| chat_modify_area, | |
| chatbot, | |
| status_bar | |
| ] | |
| ).then( | |
| fn=lambda: (gr.update(visible=False), gr.update(visible=True)), | |
| outputs=[input_area, task_confirm_area] | |
| ) | |
| back_btn.click( | |
| fn=lambda: ( | |
| gr.update(visible=True), | |
| gr.update(visible=False), | |
| gr.update(visible=False), | |
| "Waiting for input..." | |
| ), | |
| outputs=[input_area, task_confirm_area, chat_modify_area, status_bar] | |
| ) | |
| chat_send.click( | |
| fn=self.modify_task_chat, | |
| inputs=[chat_input, chatbot], | |
| outputs=[chatbot, task_list_display] | |
| ).then( | |
| fn=lambda: "", | |
| outputs=[chat_input] | |
| ) | |
| chat_input.submit( | |
| fn=self.modify_task_chat, | |
| inputs=[chat_input, chatbot], | |
| outputs=[chatbot, task_list_display] | |
| ).then( | |
| fn=lambda: "", | |
| outputs=[chat_input] | |
| ) | |
| confirm_btn.click( | |
| fn=lambda: ( | |
| gr.update(visible=False), | |
| gr.update(visible=False), | |
| gr.update(visible=True) | |
| ), | |
| outputs=[task_confirm_area, chat_modify_area, team_area] | |
| ).then( | |
| fn=self.step2_search_pois, | |
| outputs=[ | |
| *agent_displays, | |
| reasoning_output, | |
| trip_result_display, | |
| complete_summary_output, | |
| map_output, | |
| map_tab, | |
| summary_tab, | |
| status_bar | |
| ] | |
| ).then( | |
| fn=self.step3_optimize_route, | |
| outputs=[ | |
| *agent_displays, | |
| reasoning_output, | |
| trip_result_display, | |
| complete_summary_output, | |
| map_output, | |
| map_tab, | |
| summary_tab, | |
| status_bar | |
| ] | |
| ).then( | |
| fn=lambda: ( | |
| gr.update(visible=True), | |
| gr.update(selected="summary_tab") | |
| ), | |
| outputs=[trip_result_area, tabs] | |
| ) | |
| save_settings_btn.click( | |
| fn=self.save_settings, | |
| inputs=[google_maps_key, openweather_key, anthropic_key, model_choice], | |
| outputs=[settings_status] | |
| ) | |
| return demo | |
| def main(): | |
| app = LifeFlowInteractive() | |
| demo = app.build_interface() | |
| demo.launch( | |
| server_name="0.0.0.0", | |
| server_port=7860, # HuggingFace default 7860 | |
| share=False, | |
| show_error=True | |
| ) | |
| if __name__ == "__main__": | |
| main() |