Spaces:
Running
Running
| """ | |
| LifeFlow AI - Main Application v3.1 | |
| """ | |
| import sys | |
| from pathlib import Path | |
| import gradio as gr | |
| from datetime import datetime | |
| import time as time_module | |
| # 導入配置 | |
| from config import DEFAULT_SETTINGS, APP_TITLE | |
| # 導入 UI 組件 | |
| from ui.theme import get_enhanced_css | |
| from ui.components.header import create_header, create_top_controls | |
| from ui.components.input_form import create_input_form, toggle_location_inputs | |
| from ui.components.confirmation import create_confirmation_area, create_exit_button | |
| from ui.components.results import create_team_area, create_result_area, create_tabs | |
| from ui.components.modals import create_settings_modal, create_doc_modal | |
| # 導入核心工具 | |
| from core.utils import ( | |
| create_agent_stream_output, create_agent_card_enhanced, | |
| create_task_card, create_summary_card, create_animated_map, | |
| get_reasoning_html_reversed, create_celebration_animation, | |
| create_result_visualization | |
| ) | |
| class LifeFlowAI: | |
| """LifeFlow AI - v3.1 主類""" | |
| def __init__(self): | |
| self.settings = DEFAULT_SETTINGS.copy() | |
| self.task_list = [] | |
| self.reasoning_messages = [] | |
| self.planning_completed = False | |
| self.chat_history = [] | |
| def step1_analyze_tasks(self, user_input, auto_location, lat, lon): | |
| """ | |
| Step 1: 分析任務 - 帶串流輸出 | |
| 完成後進入 Step 2 (Confirmation) | |
| """ | |
| if not user_input.strip(): | |
| return self._empty_step1_outputs() | |
| # 🔧 串流輸出 1: 開始分析 | |
| stream_str = "🤔 Analyzing your request..." | |
| stream_html = self._create_stream_html(stream_str) | |
| yield ( | |
| stream_html, "", "", get_reasoning_html_reversed(), | |
| gr.update(visible=False), gr.update(visible=False), | |
| "", # chat_history_output | |
| "Starting analysis...", | |
| *[create_agent_card_enhanced("planner", "working", "Analyzing..."), | |
| *[create_agent_card_enhanced(k, "idle", "On standby") | |
| for k in ["scout", "optimizer", "validator", "weather", "traffic"]]] | |
| ) | |
| time_module.sleep(0.5) | |
| stream_str += "\n📋 Extracting tasks from your input..." | |
| # 🔧 串流輸出 2: 提取任務 | |
| stream_html = self._create_stream_html(stream_str) | |
| yield ( | |
| stream_html, "", "", get_reasoning_html_reversed(), | |
| gr.update(visible=False), gr.update(visible=False), | |
| "", # chat_history_output | |
| "Extracting tasks...", | |
| *[create_agent_card_enhanced("planner", "working", "Extracting tasks..."), | |
| *[create_agent_card_enhanced(k, "idle", "On standby") | |
| for k in ["scout", "optimizer", "validator", "weather", "traffic"]]] | |
| ) | |
| time_module.sleep(0.5) | |
| # 模擬提取的任務 | |
| self.task_list = [ | |
| {"id": 1, "title": "Visit hospital", "priority": "HIGH", | |
| "time": "08:00-12:00", "duration": "45 minutes", "location": "Hospital nearby", "icon": "🏥"}, | |
| {"id": 2, "title": "Buy groceries", "priority": "MEDIUM", | |
| "time": "Anytime", "duration": "30 minutes", "location": "Supermarket", "icon": "🛒"}, | |
| {"id": 3, "title": "Mail package", "priority": "HIGH", | |
| "time": "Before 15:00", "duration": "20 minutes", "location": "Post office", "icon": "📮"} | |
| ] | |
| # 添加推理訊息 | |
| self._add_reasoning("planner", "Analyzing input tasks...") | |
| self._add_reasoning("planner", f"Extracted {len(self.task_list)} tasks") | |
| # 生成摘要和任務卡片 | |
| high_priority = sum(1 for t in self.task_list if t["priority"] == "HIGH") | |
| total_time = sum(int(t["duration"].split()[0]) for t in self.task_list) | |
| summary_html = create_summary_card(len(self.task_list), high_priority, total_time) | |
| task_list_html = self._generate_task_list_html() | |
| stream_str += "\n✅ Analysis complete! Please review your tasks." | |
| stream_html = self._create_stream_html(stream_str) | |
| # Step 1 完成,準備進入 Step 2 | |
| yield ( | |
| stream_html, summary_html, task_list_html, | |
| get_reasoning_html_reversed(self.reasoning_messages), | |
| gr.update(visible=True), # task_confirm_area | |
| gr.update(visible=False), # chat_input_area (先隱藏) | |
| self._generate_chat_welcome_html(), # chat_history_output | |
| "Tasks extracted successfully ✓", | |
| *[create_agent_card_enhanced("planner", "complete", "Tasks extracted"), | |
| *[create_agent_card_enhanced(k, "idle", "On standby") | |
| for k in ["scout", "optimizer", "validator", "weather", "traffic"]]] | |
| ) | |
| def modify_task_chat(self, user_message): | |
| """ | |
| 處理用戶在 Chat with LifeFlow Tab 中的修改請求 | |
| 這在 Step 2 時可用 | |
| """ | |
| if not user_message.strip(): | |
| return self._generate_chat_history_html(), self._generate_task_list_html() | |
| # 添加用戶消息 | |
| self.chat_history.append({ | |
| "role": "user", | |
| "message": user_message, | |
| "time": datetime.now().strftime("%H:%M:%S") | |
| }) | |
| # 模擬 AI 回應 | |
| time_module.sleep(0.3) | |
| ai_response = f"✅ I've updated your tasks based on your request: '{user_message}'" | |
| self.chat_history.append({ | |
| "role": "assistant", | |
| "message": ai_response, | |
| "time": datetime.now().strftime("%H:%M:%S") | |
| }) | |
| # 更新 reasoning | |
| self._add_reasoning("planner", f"User requested: {user_message}") | |
| # 返回更新的聊天歷史和任務列表 | |
| return self._generate_chat_history_html(), self._generate_task_list_html() | |
| def step2_search_pois(self): | |
| """ | |
| Step 2.5: 搜索 POI - 專家團隊開始工作 | |
| Scout Agent 開始工作 | |
| """ | |
| time_module.sleep(1) | |
| self._add_reasoning("scout", "Searching for POIs...") | |
| self._add_reasoning("scout", "Found hospital: NTU Hospital (800m, 4.8★)") | |
| self._add_reasoning("scout", "Found supermarket: PX Mart (1.2km)") | |
| reasoning_html = get_reasoning_html_reversed(self.reasoning_messages) | |
| agent_updates = [ | |
| create_agent_card_enhanced("planner", "complete", "Tasks ready"), | |
| create_agent_card_enhanced("scout", "working", "Searching POIs..."), | |
| *[create_agent_card_enhanced(k, "idle", "On standby") | |
| for k in ["optimizer", "validator", "weather", "traffic"]] | |
| ] | |
| return reasoning_html, "🗺️ Searching for locations...", *agent_updates | |
| def step3_optimize_route(self): | |
| """Step 3: 優化路線 - Optimizer 開始工作""" | |
| time_module.sleep(1) | |
| self._add_reasoning("optimizer", "Running TSPTW solver...") | |
| self._add_reasoning("optimizer", "Optimized route: Hospital → Supermarket → Post Office") | |
| report = """ | |
| ## 🎯 Optimization Complete | |
| ### Route Details: | |
| 1. **🏥 Hospital** (09:00 - 10:00) | |
| 2. **🛒 Supermarket** (10:15 - 10:45) | |
| 3. **📮 Post Office** (11:05 - 11:25) | |
| ### Metrics: | |
| - ✅ Total distance: 2.8 km | |
| - ✅ Total time: 95 minutes | |
| - ✅ All deadlines met | |
| - ✅ Minimal travel distance | |
| - ✅ Weather conditions favorable | |
| """ | |
| agent_updates = [ | |
| create_agent_card_enhanced("planner", "complete", "Analysis done"), | |
| create_agent_card_enhanced("scout", "complete", "POI search done"), | |
| create_agent_card_enhanced("optimizer", "working", "Optimizing route..."), | |
| *[create_agent_card_enhanced(k, "idle", "On standby") | |
| for k in ["validator", "weather", "traffic"]] | |
| ] | |
| reasoning_html = get_reasoning_html_reversed(self.reasoning_messages) | |
| return reasoning_html, report, "🎯 Optimizing route...", *agent_updates | |
| def step4_finalize(self): | |
| """Step 4: 完成並生成報告(含慶祝動畫和增強視覺化)""" | |
| time_module.sleep(1) | |
| self._add_reasoning("validator", "Validating route quality...") | |
| self._add_reasoning("weather", "Checking weather conditions...") | |
| self._add_reasoning("traffic", "Analyzing traffic patterns...") | |
| self.planning_completed = True | |
| # 生成慶祝動畫 | |
| celebration_html = create_celebration_animation() | |
| # 生成詳細的結果視覺化 | |
| result_html = create_result_visualization(self.task_list) + celebration_html | |
| # 簡化的 timeline 和 metrics(保留兼容性,但現在主要信息在 result_html 中) | |
| timeline_html = "<h3>🗓️ Detailed Timeline</h3><p>See the complete timeline in the result panel</p>" | |
| metrics_html = "<h3>📈 Performance Metrics</h3><p>All optimization metrics are displayed above</p>" | |
| map_fig = create_animated_map() | |
| agent_updates = [ | |
| create_agent_card_enhanced(k, "complete", "Task complete") | |
| for k in ["planner", "scout", "optimizer", "validator", "weather", "traffic"] | |
| ] | |
| return ( | |
| timeline_html, metrics_html, result_html, map_fig, | |
| gr.update(visible=True), # map_tab | |
| gr.update(visible=False), # team_area (隱藏,任務完成) | |
| "🎉 Planning complete! Review your optimized route.", | |
| *agent_updates | |
| ) | |
| def save_settings(self, google_key, weather_key, anthropic_key, model): | |
| """保存設定""" | |
| 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 successfully!" | |
| def _create_stream_html(self, message): | |
| """創建串流輸出 HTML""" | |
| return f""" | |
| <div class="stream-container"> | |
| <div class="stream-text">{message}<span class="stream-cursor"></span></div> | |
| </div> | |
| """ | |
| def _add_reasoning(self, agent, message): | |
| """添加推理訊息""" | |
| self.reasoning_messages.append({ | |
| 'agent': agent, | |
| 'message': message, | |
| 'time': datetime.now().strftime("%H:%M:%S") | |
| }) | |
| def _generate_task_list_html(self): | |
| """生成任務列表 HTML""" | |
| html = "" | |
| for task in self.task_list: | |
| html += create_task_card( | |
| task["id"], task["title"], task["priority"], | |
| task["time"], task["duration"], task["location"], task["icon"] | |
| ) | |
| return html | |
| def _generate_chat_welcome_html(self): | |
| """生成 Chat 歡迎訊息""" | |
| return """ | |
| <div style="padding: 20px; text-align: center;"> | |
| <h3 style="color: #4A90E2; margin-bottom: 10px;">💬 Chat with LifeFlow</h3> | |
| <p style="opacity: 0.8;">You can now modify your tasks by chatting with me!</p> | |
| <p style="opacity: 0.6; font-size: 0.9em;">Try: "Change task 2 to high priority" or "Add a new task"</p> | |
| </div> | |
| """ | |
| def _generate_chat_history_html(self): | |
| """生成聊天歷史 HTML""" | |
| if not self.chat_history: | |
| return self._generate_chat_welcome_html() | |
| html = '<div class="chat-history-container" style="max-height: 400px; overflow-y: auto;">' | |
| for msg in self.chat_history: | |
| role = msg["role"] | |
| message = msg["message"] | |
| time = msg["time"] | |
| if role == "user": | |
| html += f''' | |
| <div style="margin-bottom: 15px; text-align: right;"> | |
| <div style="display: inline-block; max-width: 70%; background: #E3F2FD; padding: 10px 15px; border-radius: 15px 15px 0 15px;"> | |
| <div style="font-size: 0.9em; font-weight: 500;">{message}</div> | |
| <div style="font-size: 0.75em; opacity: 0.6; margin-top: 5px;">{time}</div> | |
| </div> | |
| </div> | |
| ''' | |
| else: | |
| html += f''' | |
| <div style="margin-bottom: 15px; text-align: left;"> | |
| <div style="display: inline-block; max-width: 70%; background: #F5F5F5; padding: 10px 15px; border-radius: 15px 15px 15px 0;"> | |
| <div style="font-size: 0.85em; color: #4A90E2; font-weight: 600; margin-bottom: 5px;">🤖 LifeFlow AI</div> | |
| <div style="font-size: 0.9em;">{message}</div> | |
| <div style="font-size: 0.75em; opacity: 0.6; margin-top: 5px;">{time}</div> | |
| </div> | |
| </div> | |
| ''' | |
| html += '</div>' | |
| return html | |
| def _empty_step1_outputs(self): | |
| """返回空的 Step 1 輸出""" | |
| return ( | |
| create_agent_stream_output(), | |
| "", "", get_reasoning_html_reversed(), | |
| gr.update(visible=False), | |
| gr.update(visible=False), | |
| self._generate_chat_welcome_html(), | |
| "Please enter your tasks", | |
| *[create_agent_card_enhanced(k, "idle", "On standby") | |
| for k in ["planner", "scout", "optimizer", "validator", "weather", "traffic"]] | |
| ) | |
| def build_interface(self): | |
| """構建 Gradio 界面""" | |
| with gr.Blocks(title=APP_TITLE) as demo: | |
| # 注入 CSS 樣式 | |
| gr.HTML(get_enhanced_css()) | |
| # Header | |
| create_header() | |
| # Top Controls (Theme, Settings & Doc) | |
| theme_btn, settings_btn, doc_btn = create_top_controls() | |
| # Main Layout | |
| with gr.Row(): | |
| # ========== Left Column (主操作區) ========== | |
| with gr.Column(scale=2, min_width=400): | |
| # Step 1: Input Form | |
| (input_area, agent_stream_output, user_input, auto_location, | |
| location_inputs, lat_input, lon_input, analyze_btn) = create_input_form( | |
| create_agent_stream_output() | |
| ) | |
| # Step 2: Confirmation Area (包含 Exit 和 Ready to plan 按鈕) | |
| (task_confirm_area, task_summary_display, | |
| task_list_display, exit_btn_inline, ready_plan_btn) = create_confirmation_area() | |
| # Step 2.5/3: Team Area (取代 Confirmation Area) | |
| team_area, agent_displays = create_team_area(create_agent_card_enhanced) | |
| # Step 3: Result Area (最終結果展示) | |
| (result_area, result_display, | |
| timeline_display, metrics_display) = create_result_area(create_animated_map) | |
| # ========== Right Column (狀態 + Tabs) ========== | |
| with gr.Column(scale=3, min_width=500): | |
| status_bar = gr.Textbox( | |
| label="📊 Status", | |
| value="Waiting for input...", | |
| interactive=False, | |
| max_lines=1 | |
| ) | |
| # Tabs (包含新的 Chat with LifeFlow Tab) | |
| (tabs, report_tab, map_tab, report_output, map_output, reasoning_output, | |
| chat_input_area, chat_history_output, chat_input, chat_send) = create_tabs( | |
| create_animated_map, | |
| get_reasoning_html_reversed() | |
| ) | |
| # Modals | |
| (settings_modal, google_maps_key, openweather_key, anthropic_key, | |
| model_choice, close_settings_btn, save_settings_btn, | |
| settings_status) = create_settings_modal() | |
| doc_modal, close_doc_btn = create_doc_modal() | |
| # ============= Event Handlers ============= | |
| # Auto location toggle | |
| auto_location.change( | |
| fn=toggle_location_inputs, | |
| inputs=[auto_location], | |
| outputs=[location_inputs] | |
| ) | |
| # ====== Step 1: Analyze button ====== | |
| analyze_btn.click( | |
| fn=self.step1_analyze_tasks, | |
| inputs=[user_input, auto_location, lat_input, lon_input], | |
| outputs=[ | |
| agent_stream_output, task_summary_display, task_list_display, | |
| reasoning_output, task_confirm_area, chat_input_area, | |
| chat_history_output, status_bar, *agent_displays | |
| ] | |
| ).then( | |
| # Step 1 → Step 2: 隱藏輸入區,顯示確認區 + 開啟 Chat 輸入框 | |
| fn=lambda: ( | |
| gr.update(visible=False), # input_area | |
| gr.update(visible=True), # task_confirm_area | |
| gr.update(visible=True) # chat_input_area (開啟聊天功能) | |
| ), | |
| outputs=[input_area, task_confirm_area, chat_input_area] | |
| ) | |
| # ====== Step 2: Chat with LifeFlow (任務修改) ====== | |
| chat_send.click( | |
| fn=self.modify_task_chat, | |
| inputs=[chat_input], | |
| outputs=[chat_history_output, task_list_display] | |
| ).then( | |
| fn=lambda: "", # 清空輸入框 | |
| outputs=[chat_input] | |
| ) | |
| # ====== Step 2: Exit button ====== | |
| exit_btn_inline.click( | |
| fn=lambda: ( | |
| gr.update(visible=True), # input_area | |
| gr.update(visible=False), # task_confirm_area | |
| gr.update(visible=False), # chat_input_area (關閉聊天) | |
| gr.update(visible=False), # result_area | |
| gr.update(visible=False), # team_area | |
| gr.update(visible=False), # report_tab | |
| gr.update(visible=False), # map_tab | |
| "", # user_input | |
| create_agent_stream_output(), # agent_stream_output | |
| self._generate_chat_welcome_html(), # chat_history_output (重置) | |
| "Ready to start planning..." # status_bar | |
| ), | |
| outputs=[ | |
| input_area, task_confirm_area, chat_input_area, result_area, | |
| team_area, report_tab, map_tab, user_input, | |
| agent_stream_output, chat_history_output, status_bar | |
| ] | |
| ) | |
| # ====== Step 2 → Step 2.5: Ready to Plan button ====== | |
| ready_plan_btn.click( | |
| # 隱藏確認區和聊天輸入,顯示專家團隊,切換到 AI Conversation Tab | |
| fn=lambda: ( | |
| gr.update(visible=False), # task_confirm_area | |
| gr.update(visible=False), # chat_input_area (關閉聊天輸入) | |
| gr.update(visible=True), # team_area (顯示專家團隊) | |
| gr.update(selected="ai_conversation_tab") # 切換到 AI Conversation Tab | |
| ), | |
| outputs=[task_confirm_area, chat_input_area, team_area, tabs] | |
| ).then( | |
| # Step 2.5: Scout 開始工作 | |
| fn=self.step2_search_pois, | |
| outputs=[reasoning_output, status_bar, *agent_displays] | |
| ).then( | |
| # Step 3: Optimizer 開始工作,切換到 Full Report Tab | |
| fn=self.step3_optimize_route, | |
| outputs=[reasoning_output, report_output, status_bar, *agent_displays] | |
| ).then( | |
| # 顯示 Report 和 Map Tabs,並切換到 Full Report | |
| fn=lambda: ( | |
| gr.update(visible=True), # report_tab | |
| gr.update(visible=True), # map_tab | |
| gr.update(selected="report_tab") # 切換到 Full Report Tab | |
| ), | |
| outputs=[report_tab, map_tab, tabs] | |
| ).then( | |
| # Step 4: 完成規劃 | |
| fn=self.step4_finalize, | |
| outputs=[ | |
| timeline_display, metrics_display, result_display, | |
| map_output, map_tab, team_area, status_bar, *agent_displays | |
| ] | |
| ).then( | |
| fn=lambda: gr.update(visible=True), | |
| outputs=[result_area] | |
| ) | |
| # ====== Settings ====== | |
| settings_btn.click( | |
| fn=lambda: gr.update(visible=True), | |
| outputs=[settings_modal] | |
| ) | |
| close_settings_btn.click( | |
| fn=lambda: gr.update(visible=False), | |
| outputs=[settings_modal] | |
| ) | |
| save_settings_btn.click( | |
| fn=self.save_settings, | |
| inputs=[google_maps_key, openweather_key, anthropic_key, model_choice], | |
| outputs=[settings_status] | |
| ) | |
| # ====== Theme Toggle ====== | |
| theme_btn.click( | |
| fn=None, | |
| js=""" | |
| () => { | |
| const container = document.querySelector('.gradio-container'); | |
| if (container) { | |
| container.classList.toggle('theme-dark'); | |
| const isDark = container.classList.contains('theme-dark'); | |
| localStorage.setItem('lifeflow-theme', isDark ? 'dark' : 'light'); | |
| console.log('Theme toggled:', isDark ? 'dark' : 'light'); | |
| } | |
| } | |
| """ | |
| ) | |
| # ====== Documentation ====== | |
| doc_btn.click( | |
| fn=lambda: gr.update(visible=True), | |
| outputs=[doc_modal] | |
| ) | |
| close_doc_btn.click( | |
| fn=lambda: gr.update(visible=False), | |
| outputs=[doc_modal] | |
| ) | |
| return demo | |
| def main(): | |
| app = LifeFlowAI() | |
| demo = app.build_interface() | |
| demo.launch(server_name="0.0.0.0", server_port=7860, share=False, show_error=True) | |
| if __name__ == "__main__": | |
| main() |