LifeFlow-AI / app.py
Marco310's picture
rebuild app.py
4abc17c
raw
history blame
22 kB
"""
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()