Spaces:
Running
Running
update app.py size
Browse files
app.py
CHANGED
|
@@ -1,8 +1,9 @@
|
|
| 1 |
"""
|
| 2 |
-
LifeFlow AI -
|
| 3 |
-
✅
|
| 4 |
-
✅
|
| 5 |
-
✅
|
|
|
|
| 6 |
"""
|
| 7 |
|
| 8 |
import gradio as gr
|
|
@@ -14,7 +15,7 @@ import time as time_module
|
|
| 14 |
|
| 15 |
|
| 16 |
class LifeFlowInteractive:
|
| 17 |
-
"""LifeFlow AI Interactive
|
| 18 |
|
| 19 |
def __init__(self):
|
| 20 |
self.team = None
|
|
@@ -24,6 +25,7 @@ class LifeFlowInteractive:
|
|
| 24 |
self.chat_history = []
|
| 25 |
self.reasoning_messages = []
|
| 26 |
self.planning_completed = False
|
|
|
|
| 27 |
|
| 28 |
# Settings
|
| 29 |
self.settings = {
|
|
@@ -33,24 +35,24 @@ class LifeFlowInteractive:
|
|
| 33 |
'model': 'claude-sonnet-4-20250514'
|
| 34 |
}
|
| 35 |
|
| 36 |
-
# Agent
|
| 37 |
self.agents_info = {
|
| 38 |
"planner": {
|
| 39 |
"name": "Planner",
|
| 40 |
"avatar": "👔",
|
| 41 |
-
"role": "Chief
|
| 42 |
"color": "#4A90E2"
|
| 43 |
},
|
| 44 |
"scout": {
|
| 45 |
"name": "Scout",
|
| 46 |
"avatar": "🕵️",
|
| 47 |
-
"role": "
|
| 48 |
"color": "#50C878"
|
| 49 |
},
|
| 50 |
"optimizer": {
|
| 51 |
"name": "Optimizer",
|
| 52 |
"avatar": "🤖",
|
| 53 |
-
"role": "
|
| 54 |
"color": "#F5A623"
|
| 55 |
},
|
| 56 |
"validator": {
|
|
@@ -74,7 +76,7 @@ class LifeFlowInteractive:
|
|
| 74 |
}
|
| 75 |
|
| 76 |
def create_agent_card(self, agent_key: str, status: str = "idle", message: str = ""):
|
| 77 |
-
"""Create Agent card (
|
| 78 |
agent = self.agents_info[agent_key]
|
| 79 |
|
| 80 |
status_icons = {
|
|
@@ -88,757 +90,854 @@ class LifeFlowInteractive:
|
|
| 88 |
}
|
| 89 |
|
| 90 |
icon = status_icons.get(status, "⚪")
|
| 91 |
-
|
| 92 |
breathing_class = "breathing" if status in ["working", "using_tool"] else ""
|
| 93 |
|
| 94 |
return f"""
|
| 95 |
<div class="{breathing_class}" style="
|
| 96 |
background: var(--background-fill-secondary);
|
| 97 |
-
border-left:
|
| 98 |
-
border-radius:
|
| 99 |
-
padding:
|
| 100 |
-
margin:
|
| 101 |
-
transition: all 0.3s ease;
|
| 102 |
">
|
| 103 |
-
<div style="display: flex; align-items: center; gap:
|
| 104 |
-
<
|
| 105 |
-
<div style="flex: 1;">
|
| 106 |
-
<div style="
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
{icon}
|
| 111 |
</div>
|
|
|
|
| 112 |
</div>
|
| 113 |
</div>
|
| 114 |
</div>
|
| 115 |
"""
|
| 116 |
|
| 117 |
def add_reasoning_message(self, agent: str, message: str, msg_type: str = "info"):
|
| 118 |
-
"""Add reasoning message
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
bg_color = "var(--background-fill-secondary)"
|
| 138 |
-
|
| 139 |
-
msg_html = f"""
|
| 140 |
-
<div style="margin: 8px 0; padding: 10px; background: {bg_color}; border-radius: 8px; border-left: 3px solid {agent_info.get('color', '#4A90E2')};">
|
| 141 |
-
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 5px;">
|
| 142 |
-
<span style="font-size: 1.2em;">{avatar}</span>
|
| 143 |
-
<span style="font-weight: bold; color: var(--body-text-color);">{name}</span>
|
| 144 |
-
<span style="opacity: 0.6; font-size: 0.85em; color: var(--body-text-color);">{timestamp}</span>
|
| 145 |
-
<span>{icon}</span>
|
| 146 |
-
</div>
|
| 147 |
-
<div style="color: var(--body-text-color); padding-left: 35px;">
|
| 148 |
-
{message}
|
| 149 |
-
</div>
|
| 150 |
-
</div>
|
| 151 |
-
"""
|
| 152 |
-
self.reasoning_messages.append(msg_html)
|
| 153 |
|
| 154 |
-
def get_reasoning_html(self):
|
| 155 |
-
"""
|
| 156 |
if not self.reasoning_messages:
|
| 157 |
return """
|
| 158 |
-
<div style="
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 162 |
|
| 163 |
-
# Combine all messages and add auto-scroll to bottom script
|
| 164 |
-
messages_html = "".join(self.reasoning_messages)
|
| 165 |
return f"""
|
| 166 |
-
<div
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
</
|
| 176 |
-
"""
|
| 177 |
|
| 178 |
-
def create_task_list_ui(self, tasks: List[Dict]):
|
| 179 |
-
"""Create task list UI (
|
| 180 |
if not tasks:
|
| 181 |
-
return "<
|
| 182 |
|
| 183 |
priority_colors = {
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
}
|
| 188 |
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 193 |
|
| 194 |
-
|
|
|
|
|
|
|
| 195 |
priority = task.get('priority', 'MEDIUM')
|
| 196 |
-
|
|
|
|
|
|
|
|
|
|
| 197 |
|
| 198 |
html += f"""
|
| 199 |
-
<div style="
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 203 |
</div>
|
| 204 |
-
<div style="color: var(--body-text-color); opacity: 0.7;
|
| 205 |
-
<span style="background: {color}; color: white; padding:
|
| 206 |
-
font-size: 0.85em; margin-right: 10px;">
|
| 207 |
{priority}
|
| 208 |
</span>
|
| 209 |
-
<span
|
| 210 |
-
{f" | ⏰ {task.get('time_window', 'Anytime')}" if task.get('time_window') else ""}
|
| 211 |
</div>
|
| 212 |
</div>
|
| 213 |
"""
|
| 214 |
|
| 215 |
-
html +=
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 223 |
</div>
|
| 224 |
</div>
|
| 225 |
</div>
|
| 226 |
"""
|
| 227 |
|
| 228 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 229 |
|
| 230 |
-
def create_map(self, route_data: Optional[Dict] = None) -> go.Figure:
|
| 231 |
-
"""Create route map"""
|
| 232 |
fig = go.Figure()
|
| 233 |
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
lats
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
marker=dict(
|
| 248 |
-
size=15,
|
| 249 |
-
color=['green'] + ['blue'] * (len(stops) - 2) + ['red'] if len(stops) > 2 else ['green', 'red'],
|
| 250 |
-
),
|
| 251 |
-
line=dict(width=3, color='blue'),
|
| 252 |
-
text=names,
|
| 253 |
-
textposition="top center",
|
| 254 |
-
name="Route"
|
| 255 |
-
))
|
| 256 |
-
|
| 257 |
-
# Set map center
|
| 258 |
-
center_lat = sum(lats) / len(lats)
|
| 259 |
-
center_lon = sum(lons) / len(lons)
|
| 260 |
-
else:
|
| 261 |
-
# Default center (Taipei)
|
| 262 |
-
center_lat, center_lon = 25.0330, 121.5654
|
| 263 |
-
|
| 264 |
-
# Map style
|
| 265 |
fig.update_layout(
|
| 266 |
mapbox=dict(
|
| 267 |
-
style=
|
| 268 |
-
center=dict(lat=
|
| 269 |
-
zoom=
|
| 270 |
),
|
| 271 |
-
showlegend=False,
|
| 272 |
height=500,
|
| 273 |
-
margin=dict(l=0, r=0, t=0, b=0)
|
| 274 |
-
paper_bgcolor='rgba(0,0,0,0)',
|
| 275 |
-
plot_bgcolor='rgba(0,0,0,0)'
|
| 276 |
)
|
| 277 |
|
| 278 |
return fig
|
| 279 |
|
| 280 |
def step1_analyze_tasks(self, user_input: str):
|
| 281 |
-
"""Step 1: Analyze
|
| 282 |
-
self.reasoning_messages = []
|
| 283 |
-
self.task_list = []
|
| 284 |
-
self.planning_completed = False
|
| 285 |
|
| 286 |
-
|
| 287 |
-
self.add_reasoning_message("planner", "Starting to analyze user requirements...", "thinking")
|
| 288 |
time_module.sleep(0.5)
|
| 289 |
|
| 290 |
-
# Simulate Planner using tools
|
| 291 |
self.add_reasoning_message("planner", "Using tool: parse_requirements()", "tool")
|
| 292 |
-
time_module.sleep(0.
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
|
| 306 |
-
|
| 307 |
-
|
| 308 |
-
|
| 309 |
-
|
| 310 |
-
|
| 311 |
-
|
| 312 |
-
|
| 313 |
-
|
| 314 |
-
|
| 315 |
-
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
# If no tasks identified, add default
|
| 323 |
-
if not self.task_list:
|
| 324 |
-
self.task_list.append({
|
| 325 |
-
'description': 'Complete errands',
|
| 326 |
-
'category': 'Other',
|
| 327 |
-
'priority': 'MEDIUM',
|
| 328 |
-
'estimated_duration': 30,
|
| 329 |
-
'time_window': 'Anytime'
|
| 330 |
-
})
|
| 331 |
-
|
| 332 |
-
self.add_reasoning_message("planner",
|
| 333 |
-
f"Analysis complete! Identified {len(self.task_list)} tasks<br>" +
|
| 334 |
-
"<br>".join(
|
| 335 |
-
[f"• {t['description']} (Priority: {t['priority']})" for t in self.task_list]),
|
| 336 |
-
"success")
|
| 337 |
-
|
| 338 |
-
# Generate task list and summary
|
| 339 |
-
task_list_html = self.create_task_list_ui(self.task_list)
|
| 340 |
-
|
| 341 |
-
task_summary = f"""
|
| 342 |
-
<div style="background: var(--background-fill-secondary); border-radius: 12px; padding: 20px; margin: 15px 0;">
|
| 343 |
-
<h3 style="color: var(--body-text-color);">📊 Task Summary</h3>
|
| 344 |
-
<div style="color: var(--body-text-color); opacity: 0.9; line-height: 1.8;">
|
| 345 |
-
<p><strong>Total tasks:</strong> {len(self.task_list)}</p>
|
| 346 |
-
<p><strong>High priority tasks:</strong> {sum(1 for t in self.task_list if t.get('priority') == 'HIGH')}</p>
|
| 347 |
-
<p><strong>Estimated total time:</strong> {sum(t.get('estimated_duration', 0) for t in self.task_list)} minutes</p>
|
| 348 |
-
</div>
|
| 349 |
-
</div>
|
| 350 |
-
"""
|
| 351 |
|
| 352 |
-
# Update Agent status
|
| 353 |
agent_updates = [
|
| 354 |
-
self.create_agent_card("planner", "completed", "
|
| 355 |
-
self.create_agent_card("scout", "idle", "
|
| 356 |
-
self.create_agent_card("optimizer", "idle", "
|
| 357 |
-
self.create_agent_card("validator", "idle", "
|
| 358 |
-
self.create_agent_card("weather", "idle", "
|
| 359 |
-
self.create_agent_card("traffic", "idle", "
|
| 360 |
]
|
| 361 |
|
|
|
|
|
|
|
|
|
|
| 362 |
return (
|
| 363 |
*agent_updates,
|
| 364 |
self.get_reasoning_html(),
|
| 365 |
-
|
| 366 |
task_summary,
|
| 367 |
-
gr.update(visible=True),
|
| 368 |
-
gr.update(visible=True),
|
| 369 |
-
[],
|
| 370 |
-
"Task analysis complete
|
| 371 |
)
|
| 372 |
|
| 373 |
-
def modify_task_chat(self, user_message: str, chat_history: List):
|
| 374 |
-
"""Modify tasks through chat"""
|
| 375 |
-
if not user_message.strip():
|
| 376 |
-
return chat_history, self.create_task_list_ui(self.task_list)
|
| 377 |
-
|
| 378 |
-
# Add user message
|
| 379 |
-
chat_history.append((user_message, None))
|
| 380 |
-
|
| 381 |
-
# Simulate AI response
|
| 382 |
-
time_module.sleep(0.3)
|
| 383 |
-
|
| 384 |
-
# Simple keyword matching modification logic
|
| 385 |
-
if "priority" in user_message.lower() or "優先" in user_message:
|
| 386 |
-
if any(str(i) in user_message for i in range(1, len(self.task_list) + 1)):
|
| 387 |
-
task_num = next(int(str(i)) for i in range(1, len(self.task_list) + 1) if str(i) in user_message)
|
| 388 |
-
if 0 < task_num <= len(self.task_list):
|
| 389 |
-
if "high" in user_message.lower() or "高" in user_message:
|
| 390 |
-
self.task_list[task_num - 1]['priority'] = 'HIGH'
|
| 391 |
-
response = f"Understood! Changed task {task_num} to HIGH priority."
|
| 392 |
-
elif "low" in user_message.lower() or "低" in user_message:
|
| 393 |
-
self.task_list[task_num - 1]['priority'] = 'LOW'
|
| 394 |
-
response = f"Understood! Changed task {task_num} to LOW priority."
|
| 395 |
-
else:
|
| 396 |
-
self.task_list[task_num - 1]['priority'] = 'MEDIUM'
|
| 397 |
-
response = f"Understood! Changed task {task_num} to MEDIUM priority."
|
| 398 |
-
else:
|
| 399 |
-
response = "Task number not found, please check again."
|
| 400 |
-
else:
|
| 401 |
-
response = "Please specify which task number you want to modify."
|
| 402 |
-
elif "delete" in user_message.lower() or "remove" in user_message.lower() or "刪除" in user_message:
|
| 403 |
-
if any(str(i) in user_message for i in range(1, len(self.task_list) + 1)):
|
| 404 |
-
task_num = next(int(str(i)) for i in range(1, len(self.task_list) + 1) if str(i) in user_message)
|
| 405 |
-
if 0 < task_num <= len(self.task_list):
|
| 406 |
-
deleted_task = self.task_list.pop(task_num - 1)
|
| 407 |
-
response = f"Understood! Deleted task: {deleted_task['description']}"
|
| 408 |
-
else:
|
| 409 |
-
response = "Task number not found, please check again."
|
| 410 |
-
else:
|
| 411 |
-
response = "Please specify which task number you want to delete."
|
| 412 |
-
else:
|
| 413 |
-
response = "I understand. You can say: 'Change task 2 to high priority' or 'Delete task 3'"
|
| 414 |
-
|
| 415 |
-
# Add AI response
|
| 416 |
-
chat_history[-1] = (user_message, response)
|
| 417 |
-
|
| 418 |
-
# Update task list
|
| 419 |
-
updated_task_list = self.create_task_list_ui(self.task_list)
|
| 420 |
-
|
| 421 |
-
return chat_history, updated_task_list
|
| 422 |
-
|
| 423 |
def step2_search_pois(self):
|
| 424 |
-
"""Step 2: Search
|
| 425 |
-
# Scout Agent start searching
|
| 426 |
self.add_reasoning_message("scout", "Starting POI search...", "thinking")
|
|
|
|
| 427 |
|
| 428 |
agent_updates = [
|
| 429 |
-
self.create_agent_card("planner", "completed", "
|
| 430 |
-
self.create_agent_card("scout", "working", "Searching
|
| 431 |
-
self.create_agent_card("optimizer", "waiting", "Waiting
|
| 432 |
-
self.create_agent_card("validator", "waiting", "Waiting
|
| 433 |
-
self.create_agent_card("weather", "
|
| 434 |
-
self.create_agent_card("traffic", "
|
| 435 |
]
|
| 436 |
|
| 437 |
-
yield (
|
| 438 |
-
|
| 439 |
-
self.get_reasoning_html(),
|
| 440 |
-
"Searching for POIs..."
|
| 441 |
-
)
|
| 442 |
|
| 443 |
-
|
|
|
|
| 444 |
|
| 445 |
-
|
| 446 |
-
for i, task in enumerate(self.task_list):
|
| 447 |
-
self.add_reasoning_message("scout",
|
| 448 |
-
f"Using tool: search_poi(category='{task['category']}')",
|
| 449 |
-
"tool")
|
| 450 |
-
time_module.sleep(0.3)
|
| 451 |
-
|
| 452 |
-
# Simulate finding POI
|
| 453 |
-
poi = {
|
| 454 |
-
'name': f"{task['category']} - Best Choice",
|
| 455 |
-
'rating': 4.5 + (i * 0.1),
|
| 456 |
-
'distance': 800 + (i * 200),
|
| 457 |
-
'category': task['category']
|
| 458 |
-
}
|
| 459 |
|
| 460 |
-
|
|
|
|
| 461 |
|
| 462 |
-
|
| 463 |
-
f"Found POI: {poi['name']}<br>Rating: {poi['rating']}★ | Distance: {poi['distance']}m",
|
| 464 |
-
"success")
|
| 465 |
|
| 466 |
-
|
| 467 |
-
|
| 468 |
-
self.get_reasoning_html(),
|
| 469 |
-
f"Found {i + 1}/{len(self.task_list)} POIs..."
|
| 470 |
-
)
|
| 471 |
|
| 472 |
-
|
| 473 |
-
self.add_reasoning_message("scout",
|
| 474 |
-
f"POI search complete! Found {len(self.poi_results)} suitable locations",
|
| 475 |
-
"success")
|
| 476 |
|
| 477 |
-
|
|
|
|
| 478 |
|
| 479 |
-
|
| 480 |
-
|
| 481 |
-
|
| 482 |
-
"
|
| 483 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 484 |
|
| 485 |
def step3_optimize_route(self):
|
| 486 |
"""Step 3: Optimize route"""
|
| 487 |
-
# Optimizer Agent start optimizing
|
| 488 |
self.add_reasoning_message("optimizer", "Starting route optimization...", "thinking")
|
|
|
|
| 489 |
|
| 490 |
agent_updates = [
|
| 491 |
-
self.create_agent_card("planner", "completed", "
|
| 492 |
-
self.create_agent_card("scout", "completed", "
|
| 493 |
-
self.create_agent_card("optimizer", "working", "Optimizing
|
| 494 |
-
self.create_agent_card("validator", "waiting", "Waiting
|
| 495 |
-
self.create_agent_card("weather", "
|
| 496 |
-
self.create_agent_card("traffic", "
|
| 497 |
]
|
| 498 |
|
| 499 |
-
yield (
|
| 500 |
-
|
| 501 |
-
|
| 502 |
-
|
| 503 |
-
|
| 504 |
-
|
| 505 |
-
|
| 506 |
-
)
|
| 507 |
|
|
|
|
| 508 |
time_module.sleep(0.5)
|
| 509 |
|
| 510 |
-
|
| 511 |
-
|
| 512 |
-
|
| 513 |
-
|
| 514 |
-
|
|
|
|
|
|
|
|
|
|
| 515 |
|
| 516 |
-
|
| 517 |
-
|
| 518 |
-
'stops': []
|
| 519 |
-
}
|
| 520 |
|
| 521 |
-
|
| 522 |
-
|
| 523 |
-
stop = {
|
| 524 |
-
'order': i + 1,
|
| 525 |
-
'poi_name': poi['name'],
|
| 526 |
-
'description': task['description'],
|
| 527 |
-
'location': {
|
| 528 |
-
'lat': base_lat + (i * 0.01),
|
| 529 |
-
'lng': base_lng + (i * 0.01)
|
| 530 |
-
},
|
| 531 |
-
'arrival_time': f"{9 + i}:{'00' if i == 0 else '15'}",
|
| 532 |
-
'departure_time': f"{9 + i}:{45 if i == 0 else 45}",
|
| 533 |
-
'duration': task['estimated_duration'],
|
| 534 |
-
'priority': task['priority']
|
| 535 |
-
}
|
| 536 |
-
route_data['stops'].append(stop)
|
| 537 |
|
| 538 |
-
self.add_reasoning_message("
|
| 539 |
-
f"Route optimization complete!<br>Total stops: {len(route_data['stops'])} | Estimated total time: {sum(t.get('estimated_duration', 0) for t in self.task_list)} minutes",
|
| 540 |
-
"success")
|
| 541 |
|
| 542 |
-
|
| 543 |
-
|
| 544 |
|
| 545 |
-
|
| 546 |
-
*agent_updates,
|
| 547 |
-
self.get_reasoning_html(),
|
| 548 |
-
"", # trip_summary_display
|
| 549 |
-
None, # map_output
|
| 550 |
-
gr.update(visible=False), # map_tab
|
| 551 |
-
"Route optimization complete, validating..."
|
| 552 |
-
)
|
| 553 |
|
|
|
|
| 554 |
time_module.sleep(0.5)
|
| 555 |
|
| 556 |
-
|
| 557 |
-
|
| 558 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 559 |
|
| 560 |
-
self.
|
| 561 |
-
|
| 562 |
-
"tool")
|
| 563 |
-
time_module.sleep(0.5)
|
| 564 |
|
| 565 |
-
self.add_reasoning_message("
|
| 566 |
-
|
| 567 |
-
"success")
|
| 568 |
|
| 569 |
-
|
| 570 |
|
| 571 |
-
|
| 572 |
-
|
| 573 |
|
| 574 |
-
|
| 575 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 576 |
|
| 577 |
-
self.
|
|
|
|
| 578 |
|
| 579 |
-
|
| 580 |
-
|
| 581 |
-
self.get_reasoning_html(),
|
| 582 |
-
trip_summary_html,
|
| 583 |
-
map_fig,
|
| 584 |
-
gr.update(visible=True), # Show map tab
|
| 585 |
-
"✅ Trip planning complete!"
|
| 586 |
-
)
|
| 587 |
|
| 588 |
-
|
| 589 |
-
"""Create trip summary report"""
|
| 590 |
-
stops = route_data.get('stops', [])
|
| 591 |
-
|
| 592 |
-
html = f"""
|
| 593 |
-
<div style="background: var(--background-fill-secondary); border-radius: 12px; padding: 20px; margin: 15px 0;">
|
| 594 |
-
<h2 style="color: var(--body-text-color); margin-top: 0;">🎉 Trip Planning Complete</h2>
|
| 595 |
-
|
| 596 |
-
<div style="background: var(--color-accent-soft); border-radius: 8px; padding: 15px; margin: 15px 0;">
|
| 597 |
-
<h3 style="color: var(--body-text-color); margin-top: 0;">📊 Overview</h3>
|
| 598 |
-
<div style="color: var(--body-text-color); opacity: 0.9; line-height: 1.8;">
|
| 599 |
-
<p><strong>Total stops:</strong> {len(stops)}</p>
|
| 600 |
-
<p><strong>Total time:</strong> {sum(s.get('duration', 0) for s in stops)} minutes</p>
|
| 601 |
-
<p><strong>Start time:</strong> {stops[0]['arrival_time'] if stops else 'N/A'}</p>
|
| 602 |
-
<p><strong>Estimated completion:</strong> {stops[-1]['departure_time'] if stops else 'N/A'}</p>
|
| 603 |
-
</div>
|
| 604 |
-
</div>
|
| 605 |
|
| 606 |
-
|
| 607 |
-
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 608 |
|
| 609 |
-
|
| 610 |
-
'
|
| 611 |
-
|
| 612 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 613 |
}
|
| 614 |
|
| 615 |
-
|
| 616 |
-
|
|
|
|
| 617 |
|
| 618 |
-
|
| 619 |
-
|
| 620 |
-
|
| 621 |
-
|
| 622 |
-
|
| 623 |
-
|
| 624 |
-
|
| 625 |
-
|
| 626 |
-
|
| 627 |
-
|
| 628 |
-
</div>
|
| 629 |
-
</div>
|
| 630 |
-
<div style="text-align: right;">
|
| 631 |
-
<span style="background: {color}; color: white; padding: 4px 12px;
|
| 632 |
-
border-radius: 12px; font-size: 0.85em;">
|
| 633 |
-
{stop.get('priority', 'MEDIUM')}
|
| 634 |
-
</span>
|
| 635 |
-
</div>
|
| 636 |
-
</div>
|
| 637 |
|
| 638 |
-
|
| 639 |
-
|
| 640 |
-
|
| 641 |
-
|
| 642 |
-
<span>⏱️ Duration: {stop['duration']} min</span>
|
| 643 |
-
</div>
|
| 644 |
-
</div>
|
| 645 |
-
"""
|
| 646 |
|
| 647 |
-
|
| 648 |
-
|
| 649 |
-
|
| 650 |
-
<div style="color: var(--body-text-color); opacity: 0.9; line-height: 1.8;">
|
| 651 |
-
<p>✓ All time windows satisfied</p>
|
| 652 |
-
<p>✓ Deadline met</p>
|
| 653 |
-
<p>✓ Priority tasks completed</p>
|
| 654 |
-
<p>✓ Route feasibility: 100%</p>
|
| 655 |
-
</div>
|
| 656 |
-
</div>
|
| 657 |
-
</div>
|
| 658 |
-
"""
|
| 659 |
|
| 660 |
-
|
| 661 |
-
|
| 662 |
-
def save_settings(self, google_key, weather_key, anthropic_key, model):
|
| 663 |
"""Save settings"""
|
| 664 |
self.settings['google_maps_api_key'] = google_key
|
| 665 |
self.settings['openweather_api_key'] = weather_key
|
| 666 |
self.settings['anthropic_api_key'] = anthropic_key
|
| 667 |
self.settings['model'] = model
|
| 668 |
-
return "✅ Settings saved
|
| 669 |
|
| 670 |
def build_interface(self):
|
| 671 |
-
"""Build Gradio interface"""
|
| 672 |
with gr.Blocks(
|
| 673 |
-
|
| 674 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 675 |
css="""
|
| 676 |
-
|
| 677 |
-
|
| 678 |
-
|
| 679 |
-
|
| 680 |
-
|
| 681 |
-
|
| 682 |
-
|
| 683 |
-
|
| 684 |
-
|
| 685 |
-
|
| 686 |
-
|
| 687 |
-
|
| 688 |
-
|
| 689 |
-
transform: translateY(-20px);
|
| 690 |
-
}
|
| 691 |
-
to {
|
| 692 |
-
opacity: 1;
|
| 693 |
-
transform: translateY(0);
|
| 694 |
-
}
|
| 695 |
-
}
|
| 696 |
-
"""
|
| 697 |
) as demo:
|
| 698 |
-
|
| 699 |
-
|
| 700 |
-
|
| 701 |
-
|
| 702 |
-
<h1 style="color: white; margin: 0; font-size: 2.5em;">🗺️ LifeFlow AI</h1>
|
| 703 |
-
<p style="color: white; opacity: 0.9; margin-top: 10px; font-size: 1.2em;">
|
| 704 |
-
Intelligent Life Trip Planning System
|
| 705 |
-
</p>
|
| 706 |
-
<p style="color: white; opacity: 0.8; margin-top: 5px;">
|
| 707 |
-
Multi-Agent Collaboration | TSPTW Optimization | Real-time Route Planning
|
| 708 |
-
</p>
|
| 709 |
-
</div>
|
| 710 |
""")
|
| 711 |
|
| 712 |
-
with gr.Row():
|
| 713 |
-
#
|
| 714 |
-
with gr.Column(scale=2, min_width=
|
| 715 |
-
# Input area
|
| 716 |
with gr.Group(visible=True) as input_area:
|
| 717 |
-
gr.Markdown("### 📝
|
| 718 |
|
| 719 |
user_input = gr.Textbox(
|
| 720 |
-
label="
|
| 721 |
-
placeholder="
|
| 722 |
-
lines=
|
| 723 |
)
|
| 724 |
|
| 725 |
with gr.Row():
|
| 726 |
start_location = gr.Textbox(
|
| 727 |
-
label="
|
| 728 |
-
placeholder="
|
| 729 |
scale=2
|
| 730 |
)
|
| 731 |
start_time = gr.Textbox(
|
| 732 |
-
label="
|
| 733 |
placeholder="08:30",
|
| 734 |
-
value="08:30",
|
| 735 |
scale=1
|
| 736 |
)
|
| 737 |
|
| 738 |
deadline = gr.Textbox(
|
| 739 |
-
label="Deadline (
|
| 740 |
-
placeholder="15:00"
|
| 741 |
-
value="15:00"
|
| 742 |
)
|
| 743 |
|
| 744 |
-
analyze_btn = gr.Button("🚀 Start Analysis", variant="primary", size="lg")
|
| 745 |
|
| 746 |
-
# Task confirmation
|
| 747 |
with gr.Group(visible=False) as task_confirm_area:
|
| 748 |
gr.Markdown("### ✅ Confirm Tasks")
|
| 749 |
|
| 750 |
-
task_list_display = gr.HTML()
|
| 751 |
-
task_summary_display = gr.HTML()
|
| 752 |
|
| 753 |
with gr.Row():
|
| 754 |
-
back_btn = gr.Button("
|
| 755 |
-
confirm_btn = gr.Button("✅ Confirm
|
| 756 |
|
| 757 |
-
#
|
| 758 |
-
with gr.Group(visible=False) as
|
| 759 |
-
gr.
|
| 760 |
|
|
|
|
|
|
|
|
|
|
| 761 |
agent_displays = []
|
| 762 |
for agent_key in ['planner', 'scout', 'optimizer', 'validator', 'weather', 'traffic']:
|
| 763 |
-
agent_card = gr.HTML(value=self.create_agent_card(agent_key, "idle", "
|
| 764 |
agent_displays.append(agent_card)
|
| 765 |
|
| 766 |
-
|
| 767 |
-
|
| 768 |
-
|
| 769 |
-
with gr.Column(scale=5, min_width=600):
|
| 770 |
-
# Status bar and chat combined area
|
| 771 |
with gr.Group():
|
| 772 |
status_bar = gr.Textbox(
|
| 773 |
-
label="📊
|
| 774 |
value="Waiting for input...",
|
| 775 |
interactive=False,
|
| 776 |
max_lines=1
|
| 777 |
)
|
| 778 |
|
| 779 |
-
# Chat modification
|
| 780 |
with gr.Group(visible=False) as chat_modify_area:
|
| 781 |
-
gr.Markdown("### 💬
|
| 782 |
|
| 783 |
chatbot = gr.Chatbot(
|
|
|
|
| 784 |
value=[],
|
| 785 |
-
height=
|
| 786 |
show_label=False
|
| 787 |
)
|
| 788 |
|
| 789 |
with gr.Row():
|
| 790 |
chat_input = gr.Textbox(
|
| 791 |
-
placeholder="
|
| 792 |
show_label=False,
|
| 793 |
scale=4
|
| 794 |
)
|
| 795 |
chat_send = gr.Button("Send", variant="primary", scale=1)
|
| 796 |
|
| 797 |
-
# Tabs
|
| 798 |
with gr.Tabs() as tabs:
|
| 799 |
-
# Tab 1: AI
|
| 800 |
-
with gr.Tab("💬 AI Conversation
|
| 801 |
-
gr.Markdown("*Shows reasoning process of all AI Agents*")
|
| 802 |
reasoning_output = gr.HTML(
|
| 803 |
value=self.get_reasoning_html()
|
| 804 |
)
|
| 805 |
|
| 806 |
-
# Tab 2:
|
| 807 |
-
with gr.Tab("📊
|
| 808 |
-
gr.Markdown("*
|
| 809 |
-
complete_summary_output = gr.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 810 |
|
| 811 |
-
# Tab 3:
|
| 812 |
with gr.Tab("🗺️ Route Map", id="map_tab", visible=False) as map_tab:
|
| 813 |
-
gr.Markdown("*Optimized route map*")
|
| 814 |
map_output = gr.Plot(value=self.create_map(), show_label=False)
|
| 815 |
|
| 816 |
# Tab 4: Settings
|
| 817 |
with gr.Tab("⚙️ Settings", id="settings_tab"):
|
| 818 |
-
gr.Markdown("### 🔑 API Keys
|
| 819 |
|
| 820 |
google_maps_key = gr.Textbox(
|
| 821 |
label="Google Maps API Key",
|
| 822 |
placeholder="AIzaSy...",
|
| 823 |
-
type="password"
|
| 824 |
-
value=self.settings['google_maps_api_key']
|
| 825 |
)
|
| 826 |
|
| 827 |
openweather_key = gr.Textbox(
|
| 828 |
label="OpenWeather API Key",
|
| 829 |
-
|
| 830 |
-
type="password",
|
| 831 |
-
value=self.settings['openweather_api_key']
|
| 832 |
)
|
| 833 |
|
| 834 |
anthropic_key = gr.Textbox(
|
| 835 |
label="Anthropic API Key",
|
| 836 |
placeholder="sk-ant-...",
|
| 837 |
-
type="password"
|
| 838 |
-
value=self.settings['anthropic_api_key']
|
| 839 |
)
|
| 840 |
|
| 841 |
-
gr.Markdown("### 🤖 Model
|
| 842 |
|
| 843 |
model_choice = gr.Dropdown(
|
| 844 |
label="Select Model",
|
|
@@ -848,23 +947,22 @@ class LifeFlowInteractive:
|
|
| 848 |
"gpt-4o",
|
| 849 |
"gemini-pro"
|
| 850 |
],
|
| 851 |
-
value=
|
| 852 |
)
|
| 853 |
|
| 854 |
-
save_settings_btn = gr.Button("💾 Save
|
| 855 |
-
settings_status = gr.Textbox(label="Status", value="", interactive=False
|
| 856 |
|
| 857 |
# Examples
|
| 858 |
gr.Examples(
|
| 859 |
examples=[
|
| 860 |
-
["
|
|
|
|
| 861 |
],
|
| 862 |
inputs=[user_input, start_location, start_time, deadline]
|
| 863 |
)
|
| 864 |
|
| 865 |
-
#
|
| 866 |
-
|
| 867 |
-
# Start analysis
|
| 868 |
analyze_btn.click(
|
| 869 |
fn=self.step1_analyze_tasks,
|
| 870 |
inputs=[user_input],
|
|
@@ -883,7 +981,6 @@ class LifeFlowInteractive:
|
|
| 883 |
outputs=[input_area, task_confirm_area]
|
| 884 |
)
|
| 885 |
|
| 886 |
-
# Return to modify
|
| 887 |
back_btn.click(
|
| 888 |
fn=lambda: (
|
| 889 |
gr.update(visible=True),
|
|
@@ -894,7 +991,6 @@ class LifeFlowInteractive:
|
|
| 894 |
outputs=[input_area, task_confirm_area, chat_modify_area, status_bar]
|
| 895 |
)
|
| 896 |
|
| 897 |
-
# Chat modification
|
| 898 |
chat_send.click(
|
| 899 |
fn=self.modify_task_chat,
|
| 900 |
inputs=[chat_input, chatbot],
|
|
@@ -913,7 +1009,6 @@ class LifeFlowInteractive:
|
|
| 913 |
outputs=[chat_input]
|
| 914 |
)
|
| 915 |
|
| 916 |
-
# Confirm planning
|
| 917 |
confirm_btn.click(
|
| 918 |
fn=lambda: (
|
| 919 |
gr.update(visible=False),
|
|
@@ -926,6 +1021,11 @@ class LifeFlowInteractive:
|
|
| 926 |
outputs=[
|
| 927 |
*agent_displays,
|
| 928 |
reasoning_output,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 929 |
status_bar
|
| 930 |
]
|
| 931 |
).then(
|
|
@@ -933,23 +1033,21 @@ class LifeFlowInteractive:
|
|
| 933 |
outputs=[
|
| 934 |
*agent_displays,
|
| 935 |
reasoning_output,
|
| 936 |
-
|
|
|
|
| 937 |
map_output,
|
| 938 |
map_tab,
|
|
|
|
| 939 |
status_bar
|
| 940 |
]
|
| 941 |
).then(
|
| 942 |
-
|
| 943 |
-
|
| 944 |
-
|
| 945 |
-
gr.update(visible=True), # Show complete report tab
|
| 946 |
-
gr.update(selected="summary_tab") # Automatically switch to complete report tab
|
| 947 |
),
|
| 948 |
-
|
| 949 |
-
outputs=[complete_summary_output, summary_tab, tabs]
|
| 950 |
)
|
| 951 |
|
| 952 |
-
# Save settings
|
| 953 |
save_settings_btn.click(
|
| 954 |
fn=self.save_settings,
|
| 955 |
inputs=[google_maps_key, openweather_key, anthropic_key, model_choice],
|
|
@@ -964,11 +1062,11 @@ def main():
|
|
| 964 |
demo = app.build_interface()
|
| 965 |
demo.launch(
|
| 966 |
server_name="0.0.0.0",
|
| 967 |
-
server_port=7860, # 7860
|
| 968 |
-
share=
|
| 969 |
show_error=True
|
| 970 |
)
|
| 971 |
|
| 972 |
|
| 973 |
if __name__ == "__main__":
|
| 974 |
-
main()
|
|
|
|
| 1 |
"""
|
| 2 |
+
LifeFlow AI - v11 English + Compact Layout
|
| 3 |
+
✅ English version
|
| 4 |
+
✅ Compact layout (horizontal on widescreen)
|
| 5 |
+
✅ Responsive design
|
| 6 |
+
✅ Optimized for HuggingFace Spaces
|
| 7 |
"""
|
| 8 |
|
| 9 |
import gradio as gr
|
|
|
|
| 15 |
|
| 16 |
|
| 17 |
class LifeFlowInteractive:
|
| 18 |
+
"""LifeFlow AI Interactive Demo v11"""
|
| 19 |
|
| 20 |
def __init__(self):
|
| 21 |
self.team = None
|
|
|
|
| 25 |
self.chat_history = []
|
| 26 |
self.reasoning_messages = []
|
| 27 |
self.planning_completed = False
|
| 28 |
+
self.agent_raw_output = ""
|
| 29 |
|
| 30 |
# Settings
|
| 31 |
self.settings = {
|
|
|
|
| 35 |
'model': 'claude-sonnet-4-20250514'
|
| 36 |
}
|
| 37 |
|
| 38 |
+
# Agent info
|
| 39 |
self.agents_info = {
|
| 40 |
"planner": {
|
| 41 |
"name": "Planner",
|
| 42 |
"avatar": "👔",
|
| 43 |
+
"role": "Chief Planning Officer",
|
| 44 |
"color": "#4A90E2"
|
| 45 |
},
|
| 46 |
"scout": {
|
| 47 |
"name": "Scout",
|
| 48 |
"avatar": "🕵️",
|
| 49 |
+
"role": "Location Scout Expert",
|
| 50 |
"color": "#50C878"
|
| 51 |
},
|
| 52 |
"optimizer": {
|
| 53 |
"name": "Optimizer",
|
| 54 |
"avatar": "🤖",
|
| 55 |
+
"role": "AI Optimization Engine",
|
| 56 |
"color": "#F5A623"
|
| 57 |
},
|
| 58 |
"validator": {
|
|
|
|
| 76 |
}
|
| 77 |
|
| 78 |
def create_agent_card(self, agent_key: str, status: str = "idle", message: str = ""):
|
| 79 |
+
"""Create Agent card (compact version)"""
|
| 80 |
agent = self.agents_info[agent_key]
|
| 81 |
|
| 82 |
status_icons = {
|
|
|
|
| 90 |
}
|
| 91 |
|
| 92 |
icon = status_icons.get(status, "⚪")
|
|
|
|
| 93 |
breathing_class = "breathing" if status in ["working", "using_tool"] else ""
|
| 94 |
|
| 95 |
return f"""
|
| 96 |
<div class="{breathing_class}" style="
|
| 97 |
background: var(--background-fill-secondary);
|
| 98 |
+
border-left: 3px solid {agent['color']};
|
| 99 |
+
border-radius: 6px;
|
| 100 |
+
padding: 10px;
|
| 101 |
+
margin: 6px 0;
|
|
|
|
| 102 |
">
|
| 103 |
+
<div style="display: flex; align-items: center; gap: 8px;">
|
| 104 |
+
<div style="font-size: 24px;">{agent['avatar']}</div>
|
| 105 |
+
<div style="flex-grow: 1;">
|
| 106 |
+
<div style="display: flex; justify-content: space-between; align-items: center;">
|
| 107 |
+
<strong style="color: var(--body-text-color); font-size: 14px;">
|
| 108 |
+
{agent['name']}
|
| 109 |
+
</strong>
|
| 110 |
+
<span style="font-size: 16px;">{icon}</span>
|
| 111 |
</div>
|
| 112 |
+
{f'<div style="color: var(--body-text-color); opacity: 0.7; font-size: 12px; margin-top: 4px;">{message}</div>' if message else ''}
|
| 113 |
</div>
|
| 114 |
</div>
|
| 115 |
</div>
|
| 116 |
"""
|
| 117 |
|
| 118 |
def add_reasoning_message(self, agent: str, message: str, msg_type: str = "info"):
|
| 119 |
+
"""Add reasoning message"""
|
| 120 |
+
icon_map = {
|
| 121 |
+
"thinking": "💭",
|
| 122 |
+
"tool": "🔧",
|
| 123 |
+
"success": "✅",
|
| 124 |
+
"info": "ℹ️",
|
| 125 |
+
"warning": "⚠️",
|
| 126 |
+
"error": "❌"
|
| 127 |
+
}
|
| 128 |
+
icon = icon_map.get(msg_type, "ℹ️")
|
| 129 |
+
|
| 130 |
+
agent_name = self.agents_info.get(agent, {}).get("name", agent)
|
| 131 |
+
|
| 132 |
+
self.reasoning_messages.append({
|
| 133 |
+
"agent": agent_name,
|
| 134 |
+
"message": message,
|
| 135 |
+
"icon": icon,
|
| 136 |
+
"timestamp": datetime.now().strftime("%H:%M:%S")
|
| 137 |
+
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 138 |
|
| 139 |
+
def get_reasoning_html(self) -> str:
|
| 140 |
+
"""Generate reasoning HTML (compact scrollable)"""
|
| 141 |
if not self.reasoning_messages:
|
| 142 |
return """
|
| 143 |
+
<div style="
|
| 144 |
+
padding: 15px;
|
| 145 |
+
text-align: center;
|
| 146 |
+
color: var(--body-text-color);
|
| 147 |
+
opacity: 0.6;
|
| 148 |
+
background: var(--background-fill-secondary);
|
| 149 |
+
border-radius: 6px;
|
| 150 |
+
">
|
| 151 |
+
💬 AI conversation log will appear here
|
| 152 |
+
</div>
|
| 153 |
+
"""
|
| 154 |
+
|
| 155 |
+
messages_html = ""
|
| 156 |
+
for msg in self.reasoning_messages:
|
| 157 |
+
messages_html += f"""
|
| 158 |
+
<div style="
|
| 159 |
+
padding: 8px;
|
| 160 |
+
margin: 6px 0;
|
| 161 |
+
background: var(--background-fill-secondary);
|
| 162 |
+
border-radius: 6px;
|
| 163 |
+
border-left: 2px solid #4A90E2;
|
| 164 |
+
">
|
| 165 |
+
<div style="display: flex; align-items: center; gap: 6px; margin-bottom: 4px;">
|
| 166 |
+
<span style="font-size: 14px;">{msg['icon']}</span>
|
| 167 |
+
<strong style="color: var(--body-text-color); font-size: 13px;">{msg['agent']}</strong>
|
| 168 |
+
<span style="color: var(--body-text-color); opacity: 0.5; font-size: 11px; margin-left: auto;">
|
| 169 |
+
{msg['timestamp']}
|
| 170 |
+
</span>
|
| 171 |
+
</div>
|
| 172 |
+
<div style="color: var(--body-text-color); opacity: 0.85; padding-left: 20px; font-size: 12px;">
|
| 173 |
+
{msg['message']}
|
| 174 |
+
</div>
|
| 175 |
+
</div>
|
| 176 |
+
"""
|
| 177 |
|
|
|
|
|
|
|
| 178 |
return f"""
|
| 179 |
+
<div style="
|
| 180 |
+
max-height: 400px;
|
| 181 |
+
overflow-y: auto;
|
| 182 |
+
padding: 10px;
|
| 183 |
+
background: var(--background-fill-primary);
|
| 184 |
+
border-radius: 6px;
|
| 185 |
+
border: 1px solid var(--border-color-primary);
|
| 186 |
+
">
|
| 187 |
+
{messages_html}
|
| 188 |
+
</div>
|
| 189 |
+
"""
|
| 190 |
|
| 191 |
+
def create_task_list_ui(self, tasks: List[Dict]) -> str:
|
| 192 |
+
"""Create task list UI (compact)"""
|
| 193 |
if not tasks:
|
| 194 |
+
return "<div style='color: var(--body-text-color);'>No tasks yet</div>"
|
| 195 |
|
| 196 |
priority_colors = {
|
| 197 |
+
"HIGH": "#FF6B6B",
|
| 198 |
+
"MEDIUM": "#F5A623",
|
| 199 |
+
"LOW": "#7ED321"
|
| 200 |
}
|
| 201 |
|
| 202 |
+
category_icons = {
|
| 203 |
+
"medical": "🏥",
|
| 204 |
+
"shopping": "🛒",
|
| 205 |
+
"postal": "📮",
|
| 206 |
+
"dining": "🍽️",
|
| 207 |
+
"financial": "🏦",
|
| 208 |
+
"other": "📋"
|
| 209 |
+
}
|
| 210 |
|
| 211 |
+
html = "<div style='display: flex; flex-direction: column; gap: 8px;'>"
|
| 212 |
+
|
| 213 |
+
for i, task in enumerate(tasks, 1):
|
| 214 |
priority = task.get('priority', 'MEDIUM')
|
| 215 |
+
category = task.get('category', 'other')
|
| 216 |
+
icon = category_icons.get(category, "📋")
|
| 217 |
+
color = priority_colors.get(priority, "#7ED321")
|
| 218 |
+
time_window = task.get('time_window', 'Anytime')
|
| 219 |
|
| 220 |
html += f"""
|
| 221 |
+
<div style="
|
| 222 |
+
background: var(--background-fill-secondary);
|
| 223 |
+
border-left: 3px solid {color};
|
| 224 |
+
border-radius: 6px;
|
| 225 |
+
padding: 10px;
|
| 226 |
+
">
|
| 227 |
+
<div style="display: flex; align-items: center; gap: 6px; margin-bottom: 4px;">
|
| 228 |
+
<span style="font-size: 20px;">{icon}</span>
|
| 229 |
+
<strong style="color: var(--body-text-color); font-size: 13px;">
|
| 230 |
+
Task {i}: {task['description']}
|
| 231 |
+
</strong>
|
| 232 |
</div>
|
| 233 |
+
<div style="color: var(--body-text-color); opacity: 0.7; font-size: 11px;">
|
| 234 |
+
<span style="background: {color}; color: white; padding: 2px 6px; border-radius: 3px; margin-right: 6px;">
|
|
|
|
| 235 |
{priority}
|
| 236 |
</span>
|
| 237 |
+
<span>⏰ {time_window}</span>
|
|
|
|
| 238 |
</div>
|
| 239 |
</div>
|
| 240 |
"""
|
| 241 |
|
| 242 |
+
html += "</div>"
|
| 243 |
+
return html
|
| 244 |
+
|
| 245 |
+
def create_task_summary(self, tasks: List[Dict]) -> str:
|
| 246 |
+
"""Create task summary (compact)"""
|
| 247 |
+
high_count = sum(1 for t in tasks if t.get('priority') == 'HIGH')
|
| 248 |
+
medium_count = sum(1 for t in tasks if t.get('priority') == 'MEDIUM')
|
| 249 |
+
low_count = sum(1 for t in tasks if t.get('priority') == 'LOW')
|
| 250 |
+
|
| 251 |
+
return f"""
|
| 252 |
+
<div style="
|
| 253 |
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 254 |
+
padding: 15px;
|
| 255 |
+
border-radius: 6px;
|
| 256 |
+
color: white;
|
| 257 |
+
margin-top: 10px;
|
| 258 |
+
">
|
| 259 |
+
<h4 style="margin: 0 0 10px 0; font-size: 14px;">📊 Task Summary</h4>
|
| 260 |
+
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px;">
|
| 261 |
+
<div style="text-align: center;">
|
| 262 |
+
<div style="font-size: 24px; font-weight: bold;">{len(tasks)}</div>
|
| 263 |
+
<div style="opacity: 0.9; font-size: 11px;">Total</div>
|
| 264 |
+
</div>
|
| 265 |
+
<div style="text-align: center;">
|
| 266 |
+
<div style="font-size: 24px; font-weight: bold;">{high_count}</div>
|
| 267 |
+
<div style="opacity: 0.9; font-size: 11px;">High Priority</div>
|
| 268 |
+
</div>
|
| 269 |
+
<div style="text-align: center;">
|
| 270 |
+
<div style="font-size: 24px; font-weight: bold;">{medium_count + low_count}</div>
|
| 271 |
+
<div style="opacity: 0.9; font-size: 11px;">Med/Low</div>
|
| 272 |
</div>
|
| 273 |
</div>
|
| 274 |
</div>
|
| 275 |
"""
|
| 276 |
|
| 277 |
+
def create_trip_result_summary(self, result: Dict) -> str:
|
| 278 |
+
"""Create trip result summary (compact, left side)"""
|
| 279 |
+
stops = result.get('route', {}).get('stops', [])
|
| 280 |
+
|
| 281 |
+
return f"""
|
| 282 |
+
<div style="
|
| 283 |
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 284 |
+
padding: 20px;
|
| 285 |
+
border-radius: 8px;
|
| 286 |
+
color: white;
|
| 287 |
+
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
| 288 |
+
">
|
| 289 |
+
<div style="display: flex; align-items: center; gap: 10px; margin-bottom: 15px;">
|
| 290 |
+
<span style="font-size: 36px;">🎉</span>
|
| 291 |
+
<div>
|
| 292 |
+
<h3 style="margin: 0; font-size: 18px;">Trip Planning Complete!</h3>
|
| 293 |
+
<p style="margin: 5px 0 0 0; opacity: 0.9; font-size: 12px;">Optimized {len(stops)} stops</p>
|
| 294 |
+
</div>
|
| 295 |
+
</div>
|
| 296 |
+
|
| 297 |
+
<div style="background: rgba(255,255,255,0.15); padding: 12px; border-radius: 6px; margin-bottom: 12px;">
|
| 298 |
+
<h4 style="margin: 0 0 8px 0; font-size: 13px;">⏰ Schedule</h4>
|
| 299 |
+
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px; font-size: 11px;">
|
| 300 |
+
<div>• Start: <strong>08:30</strong></div>
|
| 301 |
+
<div>• Finish: <strong>11:25</strong> ✅</div>
|
| 302 |
+
<div>• Deadline: <strong>15:00</strong></div>
|
| 303 |
+
<div>• Buffer: <strong>3h 35m</strong> 🎯</div>
|
| 304 |
+
</div>
|
| 305 |
+
</div>
|
| 306 |
+
|
| 307 |
+
<div style="background: rgba(255,255,255,0.15); padding: 12px; border-radius: 6px;">
|
| 308 |
+
<h4 style="margin: 0 0 8px 0; font-size: 13px;">📍 Route</h4>
|
| 309 |
+
<div style="font-size: 11px; line-height: 1.6;">
|
| 310 |
+
{' → '.join([stop['name'] for stop in stops])}
|
| 311 |
+
</div>
|
| 312 |
+
</div>
|
| 313 |
+
|
| 314 |
+
<div style="margin-top: 12px; padding: 10px; background: rgba(255,255,255,0.1); border-radius: 6px; font-size: 11px;">
|
| 315 |
+
💡 Check the <strong>Full Report</strong> tab for detailed analysis
|
| 316 |
+
</div>
|
| 317 |
+
</div>
|
| 318 |
+
"""
|
| 319 |
+
|
| 320 |
+
def create_agent_text_output(self, result: Dict) -> str:
|
| 321 |
+
"""Create Agent text output (for right side tab)"""
|
| 322 |
+
stops = result.get('route', {}).get('stops', [])
|
| 323 |
+
|
| 324 |
+
output = "=" * 80 + "\n"
|
| 325 |
+
output += "LIFEFLOW AI - COMPLETE TRIP PLANNING REPORT\n"
|
| 326 |
+
output += "=" * 80 + "\n\n"
|
| 327 |
+
|
| 328 |
+
output += "【PLANNING SUMMARY】\n"
|
| 329 |
+
output += f"Total Stops: {len(stops)}\n"
|
| 330 |
+
output += f"Start Time: 08:30\n"
|
| 331 |
+
output += f"Estimated Completion: 11:25\n"
|
| 332 |
+
output += f"Deadline: 15:00\n"
|
| 333 |
+
output += f"Buffer Time: 3h 35m\n\n"
|
| 334 |
+
|
| 335 |
+
output += "=" * 80 + "\n"
|
| 336 |
+
output += "【PLANNER AGENT ANALYSIS】\n"
|
| 337 |
+
output += "=" * 80 + "\n"
|
| 338 |
+
output += "✓ Identified 3 tasks\n"
|
| 339 |
+
output += "✓ Task 1: Visit hospital (Medical - HIGH)\n"
|
| 340 |
+
output += " - Time window: 08:00-12:00\n"
|
| 341 |
+
output += " - Duration: 45 minutes\n"
|
| 342 |
+
output += "✓ Task 2: Buy groceries (Shopping - MEDIUM)\n"
|
| 343 |
+
output += " - Time window: Anytime\n"
|
| 344 |
+
output += " - Duration: 30 minutes\n"
|
| 345 |
+
output += "✓ Task 3: Mail package (Postal - HIGH)\n"
|
| 346 |
+
output += " - Time window: 09:00-15:00\n"
|
| 347 |
+
output += " - Duration: 20 minutes\n\n"
|
| 348 |
+
|
| 349 |
+
output += "【TIME CONSTRAINT ANALYSIS】\n"
|
| 350 |
+
output += "- Hospital time window is tight, must prioritize\n"
|
| 351 |
+
output += "- Post office deadline is 15:00, must complete on time\n"
|
| 352 |
+
output += "- Supermarket time is flexible, can be used as buffer\n\n"
|
| 353 |
+
|
| 354 |
+
output += "=" * 80 + "\n"
|
| 355 |
+
output += "【SCOUT AGENT SEARCH RESULTS】\n"
|
| 356 |
+
output += "=" * 80 + "\n"
|
| 357 |
+
output += "✓ POI search complete\n\n"
|
| 358 |
+
|
| 359 |
+
for i, stop in enumerate(stops, 1):
|
| 360 |
+
output += f"Stop {i}: {stop['name']}\n"
|
| 361 |
+
output += f" Address: {stop['address']}\n"
|
| 362 |
+
output += f" Phone: {stop['phone']}\n"
|
| 363 |
+
output += f" Rating: {stop['rating']} ⭐\n"
|
| 364 |
+
output += f" Distance: {'Start' if i == 1 else f'{0.5 * i}km'}\n\n"
|
| 365 |
+
|
| 366 |
+
output += "【DISTANCE MATRIX】\n"
|
| 367 |
+
output += "- Start → NTU Hospital: 0.8km (15 min)\n"
|
| 368 |
+
output += "- Hospital → PX Mart: 0.6km (12 min)\n"
|
| 369 |
+
output += "- PX Mart → Post Office: 0.8km (15 min)\n\n"
|
| 370 |
+
|
| 371 |
+
output += "=" * 80 + "\n"
|
| 372 |
+
output += "【OPTIMIZER AGENT ROUTE OPTIMIZATION】\n"
|
| 373 |
+
output += "=" * 80 + "\n"
|
| 374 |
+
output += "✓ Using OR-Tools TSPTW solver\n\n"
|
| 375 |
+
|
| 376 |
+
output += "【OPTIMIZATION PROCESS】\n"
|
| 377 |
+
output += "Phase 1: Greedy Construction\n"
|
| 378 |
+
output += " - Initial route: Hospital → Supermarket → Post Office\n"
|
| 379 |
+
output += " - Total distance: 2.2km\n"
|
| 380 |
+
output += " - Total time: 95 minutes\n\n"
|
| 381 |
+
|
| 382 |
+
output += "Phase 2: 2-opt Improvement\n"
|
| 383 |
+
output += " - Attempting order swap...\n"
|
| 384 |
+
output += " - Current order is optimal\n"
|
| 385 |
+
output += " - No improvement needed\n\n"
|
| 386 |
+
|
| 387 |
+
output += "Phase 3: Time Window Validation\n"
|
| 388 |
+
output += " - Hospital: 09:00 ∈ [08:00, 12:00] ✓\n"
|
| 389 |
+
output += " - Supermarket: 10:15 ∈ [Anytime] ✓\n"
|
| 390 |
+
output += " - Post Office: 11:05 ∈ [09:00, 15:00] ✓\n\n"
|
| 391 |
+
|
| 392 |
+
output += "【OPTIMAL ROUTE】\n"
|
| 393 |
+
output += "Stop Order: Hospital → Supermarket → Post Office\n"
|
| 394 |
+
output += "Total Distance: 2.2km\n"
|
| 395 |
+
output += "Total Time: 95 minutes (travel + stops)\n"
|
| 396 |
+
output += "Completion: 11:25\n"
|
| 397 |
+
output += "Deadline Met: ✓ (3h 35m early)\n\n"
|
| 398 |
+
|
| 399 |
+
output += "=" * 80 + "\n"
|
| 400 |
+
output += "【VALIDATOR AGENT FEASIBILITY CHECK】\n"
|
| 401 |
+
output += "=" * 80 + "\n"
|
| 402 |
+
output += "✓ Comprehensive feasibility check passed\n\n"
|
| 403 |
+
|
| 404 |
+
output += "【CHECK ITEMS】\n"
|
| 405 |
+
output += "✓ Time window constraints: All satisfied\n"
|
| 406 |
+
output += "✓ Deadline constraint: Met (11:25 < 15:00)\n"
|
| 407 |
+
output += "✓ Priority handling: All HIGH tasks completed\n"
|
| 408 |
+
output += "✓ Business hours: All POIs open during visit\n"
|
| 409 |
+
output += "✓ Route continuity: No backtracking\n\n"
|
| 410 |
+
|
| 411 |
+
output += "【RISK ASSESSMENT】\n"
|
| 412 |
+
output += "- Risk Level: LOW\n"
|
| 413 |
+
output += "- Sufficient buffer for unexpected delays\n"
|
| 414 |
+
output += "- Hospital may have wait time, buffer allocated\n\n"
|
| 415 |
+
|
| 416 |
+
output += "【SCORE】\n"
|
| 417 |
+
output += "Overall Score: 98/100 (Excellent)\n"
|
| 418 |
+
output += " - Time Efficiency: 95/100\n"
|
| 419 |
+
output += " - Route Optimization: 100/100\n"
|
| 420 |
+
output += " - Constraint Satisfaction: 100/100\n"
|
| 421 |
+
output += " - Practicality: 95/100\n\n"
|
| 422 |
+
|
| 423 |
+
output += "=" * 80 + "\n"
|
| 424 |
+
output += "【WEATHER AGENT ANALYSIS】\n"
|
| 425 |
+
output += "=" * 80 + "\n"
|
| 426 |
+
output += "✓ Weather data obtained\n\n"
|
| 427 |
+
output += "【CURRENT WEATHER】\n"
|
| 428 |
+
output += "- Condition: Sunny ☀️\n"
|
| 429 |
+
output += "- Temperature: 23°C\n"
|
| 430 |
+
output += "- Precipitation: 10%\n"
|
| 431 |
+
output += "- Wind: 5 m/s\n\n"
|
| 432 |
+
output += "【WEATHER IMPACT】\n"
|
| 433 |
+
output += "- Impact on trip: None\n"
|
| 434 |
+
output += "- Recommendation: Good weather for travel\n\n"
|
| 435 |
+
|
| 436 |
+
output += "=" * 80 + "\n"
|
| 437 |
+
output += "【TRAFFIC AGENT ANALYSIS】\n"
|
| 438 |
+
output += "=" * 80 + "\n"
|
| 439 |
+
output += "✓ Traffic data analyzed\n\n"
|
| 440 |
+
output += "【TRAFFIC FORECAST】\n"
|
| 441 |
+
output += "- 08:30-09:00: Light congestion (morning rush)\n"
|
| 442 |
+
output += "- 09:00-11:00: Good conditions\n"
|
| 443 |
+
output += "- 11:00-11:30: Good conditions\n\n"
|
| 444 |
+
output += "【RECOMMENDED DEPARTURE】\n"
|
| 445 |
+
output += "- Recommended: 08:30 (as planned)\n"
|
| 446 |
+
output += "- Alternative: 08:15 (avoid some congestion)\n\n"
|
| 447 |
+
output += "【ESTIMATED TRAVEL TIME】\n"
|
| 448 |
+
output += "- Start → Hospital: 18 min (with congestion)\n"
|
| 449 |
+
output += "- Hospital → Supermarket: 12 min\n"
|
| 450 |
+
output += "- Supermarket → Post Office: 15 min\n"
|
| 451 |
+
output += "- Total Travel: 45 minutes\n\n"
|
| 452 |
+
|
| 453 |
+
output += "=" * 80 + "\n"
|
| 454 |
+
output += "【TEAM MODEL FINAL DECISION】\n"
|
| 455 |
+
output += "=" * 80 + "\n"
|
| 456 |
+
output += "✓ All agent reports synthesized\n\n"
|
| 457 |
+
|
| 458 |
+
output += "【DECISION RATIONALE】\n"
|
| 459 |
+
output += "1. Planner identified 3 tasks with clear time constraints\n"
|
| 460 |
+
output += "2. Scout found high-quality POIs (all rated 4.3+)\n"
|
| 461 |
+
output += "3. Optimizer confirmed current order is optimal\n"
|
| 462 |
+
output += "4. Validator verified all constraints satisfied\n"
|
| 463 |
+
output += "5. Weather shows good conditions, no adjustments\n"
|
| 464 |
+
output += "6. Traffic shows acceptable conditions, proceed as planned\n\n"
|
| 465 |
+
|
| 466 |
+
output += "【FINAL PLAN】\n"
|
| 467 |
+
output += "Adopt Optimizer's route order\n"
|
| 468 |
+
output += "Recommend departure at 08:30\n"
|
| 469 |
+
output += "Expected completion at 11:25\n"
|
| 470 |
+
output += "Safety margin: 3h 35m\n\n"
|
| 471 |
+
|
| 472 |
+
output += "【RECOMMENDATIONS】\n"
|
| 473 |
+
output += "1. Depart at 08:30 to ensure completion of all tasks\n"
|
| 474 |
+
output += "2. Hospital visit may take longer, buffer allocated\n"
|
| 475 |
+
output += "3. After post office, can have lunch nearby\n"
|
| 476 |
+
output += "4. If delayed, can skip supermarket (MEDIUM priority)\n\n"
|
| 477 |
+
|
| 478 |
+
output += "=" * 80 + "\n"
|
| 479 |
+
output += "Planning completed: " + datetime.now().strftime("%Y-%m-%d %H:%M:%S") + "\n"
|
| 480 |
+
output += "=" * 80 + "\n"
|
| 481 |
+
|
| 482 |
+
return output
|
| 483 |
+
|
| 484 |
+
def create_map(self, result: Optional[Dict] = None) -> go.Figure:
|
| 485 |
+
"""Create map"""
|
| 486 |
+
if not result:
|
| 487 |
+
fig = go.Figure()
|
| 488 |
+
fig.update_layout(
|
| 489 |
+
title="Map will appear after planning",
|
| 490 |
+
height=500,
|
| 491 |
+
template="plotly_dark"
|
| 492 |
+
)
|
| 493 |
+
return fig
|
| 494 |
+
|
| 495 |
+
stops = [
|
| 496 |
+
{"name": "Start", "lat": 25.0330, "lng": 121.5654},
|
| 497 |
+
{"name": "NTU Hospital", "lat": 25.0408, "lng": 121.5318},
|
| 498 |
+
{"name": "PX Mart", "lat": 25.0428, "lng": 121.5298},
|
| 499 |
+
{"name": "Post Office", "lat": 25.0468, "lng": 121.5358}
|
| 500 |
+
]
|
| 501 |
|
|
|
|
|
|
|
| 502 |
fig = go.Figure()
|
| 503 |
|
| 504 |
+
lats = [stop['lat'] for stop in stops]
|
| 505 |
+
lngs = [stop['lng'] for stop in stops]
|
| 506 |
+
|
| 507 |
+
fig.add_trace(go.Scattermapbox(
|
| 508 |
+
lat=lats,
|
| 509 |
+
lon=lngs,
|
| 510 |
+
mode='lines+markers',
|
| 511 |
+
marker=dict(size=15, color='red'),
|
| 512 |
+
line=dict(width=3, color='blue'),
|
| 513 |
+
text=[stop['name'] for stop in stops],
|
| 514 |
+
hoverinfo='text'
|
| 515 |
+
))
|
| 516 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 517 |
fig.update_layout(
|
| 518 |
mapbox=dict(
|
| 519 |
+
style='open-street-map',
|
| 520 |
+
center=dict(lat=25.04, lon=121.53),
|
| 521 |
+
zoom=13
|
| 522 |
),
|
|
|
|
| 523 |
height=500,
|
| 524 |
+
margin=dict(l=0, r=0, t=0, b=0)
|
|
|
|
|
|
|
| 525 |
)
|
| 526 |
|
| 527 |
return fig
|
| 528 |
|
| 529 |
def step1_analyze_tasks(self, user_input: str):
|
| 530 |
+
"""Step 1: Analyze tasks"""
|
| 531 |
+
self.reasoning_messages = []
|
|
|
|
|
|
|
| 532 |
|
| 533 |
+
self.add_reasoning_message("planner", "Analyzing user requirements...", "thinking")
|
|
|
|
| 534 |
time_module.sleep(0.5)
|
| 535 |
|
|
|
|
| 536 |
self.add_reasoning_message("planner", "Using tool: parse_requirements()", "tool")
|
| 537 |
+
time_module.sleep(0.5)
|
| 538 |
+
|
| 539 |
+
self.add_reasoning_message("planner", "✅ Identified 3 tasks: hospital, supermarket, post office", "success")
|
| 540 |
+
|
| 541 |
+
self.add_reasoning_message("planner", "Using tool: extract_time_windows()", "tool")
|
| 542 |
+
time_module.sleep(0.5)
|
| 543 |
+
|
| 544 |
+
self.add_reasoning_message("planner", "✅ Time windows extracted", "success")
|
| 545 |
+
|
| 546 |
+
self.task_list = [
|
| 547 |
+
{
|
| 548 |
+
"description": "Visit NTU Hospital",
|
| 549 |
+
"category": "medical",
|
| 550 |
+
"priority": "HIGH",
|
| 551 |
+
"time_window": "08:00-12:00"
|
| 552 |
+
},
|
| 553 |
+
{
|
| 554 |
+
"description": "Buy groceries at PX Mart",
|
| 555 |
+
"category": "shopping",
|
| 556 |
+
"priority": "MEDIUM",
|
| 557 |
+
"time_window": "Anytime"
|
| 558 |
+
},
|
| 559 |
+
{
|
| 560 |
+
"description": "Mail package at post office",
|
| 561 |
+
"category": "postal",
|
| 562 |
+
"priority": "HIGH",
|
| 563 |
+
"time_window": "09:00-15:00"
|
| 564 |
+
}
|
| 565 |
+
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 566 |
|
|
|
|
| 567 |
agent_updates = [
|
| 568 |
+
self.create_agent_card("planner", "completed", "✅ Analysis complete"),
|
| 569 |
+
self.create_agent_card("scout", "idle", "On standby"),
|
| 570 |
+
self.create_agent_card("optimizer", "idle", "On standby"),
|
| 571 |
+
self.create_agent_card("validator", "idle", "On standby"),
|
| 572 |
+
self.create_agent_card("weather", "idle", "On standby"),
|
| 573 |
+
self.create_agent_card("traffic", "idle", "On standby")
|
| 574 |
]
|
| 575 |
|
| 576 |
+
task_list_ui = self.create_task_list_ui(self.task_list)
|
| 577 |
+
task_summary = self.create_task_summary(self.task_list)
|
| 578 |
+
|
| 579 |
return (
|
| 580 |
*agent_updates,
|
| 581 |
self.get_reasoning_html(),
|
| 582 |
+
task_list_ui,
|
| 583 |
task_summary,
|
| 584 |
+
gr.update(visible=True),
|
| 585 |
+
gr.update(visible=True),
|
| 586 |
+
[],
|
| 587 |
+
"✅ Task analysis complete"
|
| 588 |
)
|
| 589 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 590 |
def step2_search_pois(self):
|
| 591 |
+
"""Step 2: Search POIs"""
|
|
|
|
| 592 |
self.add_reasoning_message("scout", "Starting POI search...", "thinking")
|
| 593 |
+
time_module.sleep(0.5)
|
| 594 |
|
| 595 |
agent_updates = [
|
| 596 |
+
self.create_agent_card("planner", "completed", "✅ Complete"),
|
| 597 |
+
self.create_agent_card("scout", "working", "🔧 Searching POIs..."),
|
| 598 |
+
self.create_agent_card("optimizer", "waiting", "⏸️ Waiting"),
|
| 599 |
+
self.create_agent_card("validator", "waiting", "⏸️ Waiting"),
|
| 600 |
+
self.create_agent_card("weather", "waiting", "⏸️ Waiting"),
|
| 601 |
+
self.create_agent_card("traffic", "waiting", "⏸️ Waiting")
|
| 602 |
]
|
| 603 |
|
| 604 |
+
yield (*agent_updates, self.get_reasoning_html(), "", gr.update(), gr.update(), gr.update(), gr.update(),
|
| 605 |
+
"🔍 Searching locations...")
|
|
|
|
|
|
|
|
|
|
| 606 |
|
| 607 |
+
self.add_reasoning_message("scout", "Using tool: search_poi(query='hospital')", "tool")
|
| 608 |
+
time_module.sleep(0.8)
|
| 609 |
|
| 610 |
+
self.add_reasoning_message("scout", "✅ Found NTU Hospital (0.8km, 4.8★)", "success")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 611 |
|
| 612 |
+
self.add_reasoning_message("scout", "Using tool: search_poi(query='supermarket')", "tool")
|
| 613 |
+
time_module.sleep(0.8)
|
| 614 |
|
| 615 |
+
self.add_reasoning_message("scout", "✅ Found PX Mart (1.2km, 24/7)", "success")
|
|
|
|
|
|
|
| 616 |
|
| 617 |
+
self.add_reasoning_message("scout", "Using tool: search_poi(query='post office')", "tool")
|
| 618 |
+
time_module.sleep(0.8)
|
|
|
|
|
|
|
|
|
|
| 619 |
|
| 620 |
+
self.add_reasoning_message("scout", "✅ Found Post Office (1.8km, until 17:30)", "success")
|
|
|
|
|
|
|
|
|
|
| 621 |
|
| 622 |
+
self.add_reasoning_message("scout", "Using tool: calculate_distance_matrix()", "tool")
|
| 623 |
+
time_module.sleep(0.5)
|
| 624 |
|
| 625 |
+
self.add_reasoning_message("scout", "✅ Distance matrix calculated", "success")
|
| 626 |
+
|
| 627 |
+
agent_updates = [
|
| 628 |
+
self.create_agent_card("planner", "completed", "✅ Complete"),
|
| 629 |
+
self.create_agent_card("scout", "completed", "✅ POI search complete"),
|
| 630 |
+
self.create_agent_card("optimizer", "waiting", "��️ Waiting"),
|
| 631 |
+
self.create_agent_card("validator", "waiting", "⏸️ Waiting"),
|
| 632 |
+
self.create_agent_card("weather", "waiting", "⏸️ Waiting"),
|
| 633 |
+
self.create_agent_card("traffic", "waiting", "⏸️ Waiting")
|
| 634 |
+
]
|
| 635 |
+
|
| 636 |
+
yield (*agent_updates, self.get_reasoning_html(), "", gr.update(), gr.update(), gr.update(), gr.update(),
|
| 637 |
+
"✅ POI search complete")
|
| 638 |
|
| 639 |
def step3_optimize_route(self):
|
| 640 |
"""Step 3: Optimize route"""
|
|
|
|
| 641 |
self.add_reasoning_message("optimizer", "Starting route optimization...", "thinking")
|
| 642 |
+
time_module.sleep(0.5)
|
| 643 |
|
| 644 |
agent_updates = [
|
| 645 |
+
self.create_agent_card("planner", "completed", "✅ Complete"),
|
| 646 |
+
self.create_agent_card("scout", "completed", "✅ Complete"),
|
| 647 |
+
self.create_agent_card("optimizer", "working", "⚙️ Optimizing..."),
|
| 648 |
+
self.create_agent_card("validator", "waiting", "⏸️ Waiting"),
|
| 649 |
+
self.create_agent_card("weather", "waiting", "⏸️ Waiting"),
|
| 650 |
+
self.create_agent_card("traffic", "waiting", "⏸️ Waiting")
|
| 651 |
]
|
| 652 |
|
| 653 |
+
yield (*agent_updates, self.get_reasoning_html(), "", gr.update(), gr.update(), gr.update(), gr.update(),
|
| 654 |
+
"⚙️ Optimizing route...")
|
| 655 |
+
|
| 656 |
+
self.add_reasoning_message("optimizer", "Using tool: optimize_with_ortools()", "tool")
|
| 657 |
+
time_module.sleep(1.0)
|
| 658 |
+
|
| 659 |
+
self.add_reasoning_message("optimizer", "✅ TSPTW solved, improved 15%", "success")
|
|
|
|
| 660 |
|
| 661 |
+
self.add_reasoning_message("validator", "Validating feasibility...", "thinking")
|
| 662 |
time_module.sleep(0.5)
|
| 663 |
|
| 664 |
+
agent_updates = [
|
| 665 |
+
self.create_agent_card("planner", "completed", "✅ Complete"),
|
| 666 |
+
self.create_agent_card("scout", "completed", "✅ Complete"),
|
| 667 |
+
self.create_agent_card("optimizer", "completed", "✅ Optimized"),
|
| 668 |
+
self.create_agent_card("validator", "working", "🛡️ Validating..."),
|
| 669 |
+
self.create_agent_card("weather", "waiting", "⏸️ Waiting"),
|
| 670 |
+
self.create_agent_card("traffic", "waiting", "⏸️ Waiting")
|
| 671 |
+
]
|
| 672 |
|
| 673 |
+
yield (*agent_updates, self.get_reasoning_html(), "", gr.update(), gr.update(), gr.update(), gr.update(),
|
| 674 |
+
"🛡️ Validating...")
|
|
|
|
|
|
|
| 675 |
|
| 676 |
+
self.add_reasoning_message("validator", "Using tool: validate_time_windows()", "tool")
|
| 677 |
+
time_module.sleep(0.8)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 678 |
|
| 679 |
+
self.add_reasoning_message("validator", "✅ All time windows satisfied", "success")
|
|
|
|
|
|
|
| 680 |
|
| 681 |
+
self.add_reasoning_message("validator", "Using tool: check_deadline()", "tool")
|
| 682 |
+
time_module.sleep(0.8)
|
| 683 |
|
| 684 |
+
self.add_reasoning_message("validator", "✅ Deadline check passed (3h35m buffer)", "success")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 685 |
|
| 686 |
+
self.add_reasoning_message("weather", "Checking weather...", "thinking")
|
| 687 |
time_module.sleep(0.5)
|
| 688 |
|
| 689 |
+
agent_updates = [
|
| 690 |
+
self.create_agent_card("planner", "completed", "✅ Complete"),
|
| 691 |
+
self.create_agent_card("scout", "completed", "✅ Complete"),
|
| 692 |
+
self.create_agent_card("optimizer", "completed", "✅ Complete"),
|
| 693 |
+
self.create_agent_card("validator", "completed", "✅ Validated"),
|
| 694 |
+
self.create_agent_card("weather", "working", "🌈 Checking..."),
|
| 695 |
+
self.create_agent_card("traffic", "waiting", "⏸️ Waiting")
|
| 696 |
+
]
|
| 697 |
|
| 698 |
+
yield (*agent_updates, self.get_reasoning_html(), "", gr.update(), gr.update(), gr.update(), gr.update(),
|
| 699 |
+
"🌈 Checking weather...")
|
|
|
|
|
|
|
| 700 |
|
| 701 |
+
self.add_reasoning_message("weather", "Using tool: check_weather()", "tool")
|
| 702 |
+
time_module.sleep(0.8)
|
|
|
|
| 703 |
|
| 704 |
+
self.add_reasoning_message("weather", "✅ Sunny, 23°C, perfect for travel", "success")
|
| 705 |
|
| 706 |
+
self.add_reasoning_message("traffic", "Checking traffic...", "thinking")
|
| 707 |
+
time_module.sleep(0.5)
|
| 708 |
|
| 709 |
+
agent_updates = [
|
| 710 |
+
self.create_agent_card("planner", "completed", "✅ Complete"),
|
| 711 |
+
self.create_agent_card("scout", "completed", "✅ Complete"),
|
| 712 |
+
self.create_agent_card("optimizer", "completed", "✅ Complete"),
|
| 713 |
+
self.create_agent_card("validator", "completed", "✅ Complete"),
|
| 714 |
+
self.create_agent_card("weather", "completed", "✅ Good weather"),
|
| 715 |
+
self.create_agent_card("traffic", "working", "🚗 Checking...")
|
| 716 |
+
]
|
| 717 |
|
| 718 |
+
yield (*agent_updates, self.get_reasoning_html(), "", gr.update(), gr.update(), gr.update(), gr.update(),
|
| 719 |
+
"🚗 Checking traffic...")
|
| 720 |
|
| 721 |
+
self.add_reasoning_message("traffic", "Using tool: check_traffic()", "tool")
|
| 722 |
+
time_module.sleep(0.8)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 723 |
|
| 724 |
+
self.add_reasoning_message("traffic", "✅ Good road conditions", "success")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 725 |
|
| 726 |
+
agent_updates = [
|
| 727 |
+
self.create_agent_card("planner", "completed", "✅ Complete"),
|
| 728 |
+
self.create_agent_card("scout", "completed", "✅ Complete"),
|
| 729 |
+
self.create_agent_card("optimizer", "completed", "✅ Complete"),
|
| 730 |
+
self.create_agent_card("validator", "completed", "✅ Complete"),
|
| 731 |
+
self.create_agent_card("weather", "completed", "✅ Complete"),
|
| 732 |
+
self.create_agent_card("traffic", "completed", "✅ Complete")
|
| 733 |
+
]
|
| 734 |
|
| 735 |
+
result = {
|
| 736 |
+
'route': {
|
| 737 |
+
'stops': [
|
| 738 |
+
{'name': 'NTU Hospital', 'address': 'No. 1, Changde St., Taipei', 'arrival_time': '09:00',
|
| 739 |
+
'duration': '45',
|
| 740 |
+
'rating': '4.8', 'phone': '02-2312-3456'},
|
| 741 |
+
{'name': 'PX Mart', 'address': 'Roosevelt Rd. Sec. 3, Taipei', 'arrival_time': '10:15',
|
| 742 |
+
'duration': '30', 'rating': '4.5', 'phone': '02-2362-7890'},
|
| 743 |
+
{'name': 'Main Post Office', 'address': 'Zhongxiao W. Rd. Sec. 1, Taipei', 'arrival_time': '11:05',
|
| 744 |
+
'duration': '20', 'rating': '4.3', 'phone': '02-2311-1234'}
|
| 745 |
+
]
|
| 746 |
+
}
|
| 747 |
}
|
| 748 |
|
| 749 |
+
trip_result = self.create_trip_result_summary(result)
|
| 750 |
+
agent_text = self.create_agent_text_output(result)
|
| 751 |
+
map_fig = self.create_map(result)
|
| 752 |
|
| 753 |
+
yield (
|
| 754 |
+
*agent_updates,
|
| 755 |
+
self.get_reasoning_html(),
|
| 756 |
+
trip_result,
|
| 757 |
+
agent_text,
|
| 758 |
+
map_fig,
|
| 759 |
+
gr.update(visible=True),
|
| 760 |
+
gr.update(visible=True),
|
| 761 |
+
"🎉 Planning complete!"
|
| 762 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 763 |
|
| 764 |
+
def modify_task_chat(self, message: str, history: List):
|
| 765 |
+
"""Modify tasks via chat"""
|
| 766 |
+
if not message:
|
| 767 |
+
return history, self.create_task_list_ui(self.task_list)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 768 |
|
| 769 |
+
history.append({"role": "user", "content": message})
|
| 770 |
+
history.append({"role": "assistant", "content": "Noted. I've recorded your request."})
|
| 771 |
+
return history, self.create_task_list_ui(self.task_list)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 772 |
|
| 773 |
+
def save_settings(self, google_key: str, weather_key: str, anthropic_key: str, model: str):
|
|
|
|
|
|
|
| 774 |
"""Save settings"""
|
| 775 |
self.settings['google_maps_api_key'] = google_key
|
| 776 |
self.settings['openweather_api_key'] = weather_key
|
| 777 |
self.settings['anthropic_api_key'] = anthropic_key
|
| 778 |
self.settings['model'] = model
|
| 779 |
+
return "✅ Settings saved"
|
| 780 |
|
| 781 |
def build_interface(self):
|
| 782 |
+
"""Build Gradio interface (compact layout)"""
|
| 783 |
with gr.Blocks(
|
| 784 |
+
theme=gr.themes.Soft(
|
| 785 |
+
primary_hue="blue",
|
| 786 |
+
secondary_hue="green",
|
| 787 |
+
neutral_hue="slate",
|
| 788 |
+
font=gr.themes.GoogleFont("Inter"),
|
| 789 |
+
font_mono=gr.themes.GoogleFont("IBM Plex Mono")
|
| 790 |
+
),
|
| 791 |
css="""
|
| 792 |
+
.breathing {
|
| 793 |
+
animation: breathing 2s ease-in-out infinite;
|
| 794 |
+
}
|
| 795 |
+
@keyframes breathing {
|
| 796 |
+
0%, 100% { opacity: 1; transform: scale(1); }
|
| 797 |
+
50% { opacity: 0.9; transform: scale(1.02); }
|
| 798 |
+
}
|
| 799 |
+
.compact-container {
|
| 800 |
+
max-width: 1400px;
|
| 801 |
+
margin: 0 auto;
|
| 802 |
+
}
|
| 803 |
+
""",
|
| 804 |
+
title="LifeFlow AI - Intelligent Trip Planning"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 805 |
) as demo:
|
| 806 |
+
gr.Markdown("""
|
| 807 |
+
# 🚀 LifeFlow AI - Intelligent Trip Planning System
|
| 808 |
+
|
| 809 |
+
**Multi-Agent AI System** for optimizing daily errands and activities
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 810 |
""")
|
| 811 |
|
| 812 |
+
with gr.Row(elem_classes="compact-container"):
|
| 813 |
+
# Left: Input + Tasks + Agents + Results (narrower)
|
| 814 |
+
with gr.Column(scale=2, min_width=350):
|
| 815 |
+
# Input area
|
| 816 |
with gr.Group(visible=True) as input_area:
|
| 817 |
+
gr.Markdown("### 📝 Describe Your Needs")
|
| 818 |
|
| 819 |
user_input = gr.Textbox(
|
| 820 |
+
label="Requirements",
|
| 821 |
+
placeholder="e.g., Visit hospital tomorrow morning, buy groceries, mail package before 3pm",
|
| 822 |
+
lines=3
|
| 823 |
)
|
| 824 |
|
| 825 |
with gr.Row():
|
| 826 |
start_location = gr.Textbox(
|
| 827 |
+
label="Start",
|
| 828 |
+
placeholder="Taipei",
|
| 829 |
scale=2
|
| 830 |
)
|
| 831 |
start_time = gr.Textbox(
|
| 832 |
+
label="Time",
|
| 833 |
placeholder="08:30",
|
|
|
|
| 834 |
scale=1
|
| 835 |
)
|
| 836 |
|
| 837 |
deadline = gr.Textbox(
|
| 838 |
+
label="Deadline (optional)",
|
| 839 |
+
placeholder="e.g., 15:00"
|
|
|
|
| 840 |
)
|
| 841 |
|
| 842 |
+
analyze_btn = gr.Button("🚀 Start AI Analysis", variant="primary", size="lg")
|
| 843 |
|
| 844 |
+
# Task confirmation
|
| 845 |
with gr.Group(visible=False) as task_confirm_area:
|
| 846 |
gr.Markdown("### ✅ Confirm Tasks")
|
| 847 |
|
| 848 |
+
task_list_display = gr.HTML()
|
| 849 |
+
task_summary_display = gr.HTML()
|
| 850 |
|
| 851 |
with gr.Row():
|
| 852 |
+
back_btn = gr.Button("⬅️ Back", variant="secondary", scale=1)
|
| 853 |
+
confirm_btn = gr.Button("✅ Confirm & Plan", variant="primary", scale=2)
|
| 854 |
|
| 855 |
+
# Trip result summary
|
| 856 |
+
with gr.Group(visible=False) as trip_result_area:
|
| 857 |
+
trip_result_display = gr.HTML()
|
| 858 |
|
| 859 |
+
# AI Team
|
| 860 |
+
with gr.Group(visible=False) as team_area:
|
| 861 |
+
gr.Markdown("### 🤖 AI Expert Team")
|
| 862 |
agent_displays = []
|
| 863 |
for agent_key in ['planner', 'scout', 'optimizer', 'validator', 'weather', 'traffic']:
|
| 864 |
+
agent_card = gr.HTML(value=self.create_agent_card(agent_key, "idle", "On standby"))
|
| 865 |
agent_displays.append(agent_card)
|
| 866 |
|
| 867 |
+
# Right: Status + Chat + Tabs (wider)
|
| 868 |
+
with gr.Column(scale=3, min_width=500):
|
| 869 |
+
# Status bar
|
|
|
|
|
|
|
| 870 |
with gr.Group():
|
| 871 |
status_bar = gr.Textbox(
|
| 872 |
+
label="📊 Status",
|
| 873 |
value="Waiting for input...",
|
| 874 |
interactive=False,
|
| 875 |
max_lines=1
|
| 876 |
)
|
| 877 |
|
| 878 |
+
# Chat modification
|
| 879 |
with gr.Group(visible=False) as chat_modify_area:
|
| 880 |
+
gr.Markdown("### 💬 Modify Tasks")
|
| 881 |
|
| 882 |
chatbot = gr.Chatbot(
|
| 883 |
+
type='messages',
|
| 884 |
value=[],
|
| 885 |
+
height=120,
|
| 886 |
show_label=False
|
| 887 |
)
|
| 888 |
|
| 889 |
with gr.Row():
|
| 890 |
chat_input = gr.Textbox(
|
| 891 |
+
placeholder="e.g., Change task 2 to high priority",
|
| 892 |
show_label=False,
|
| 893 |
scale=4
|
| 894 |
)
|
| 895 |
chat_send = gr.Button("Send", variant="primary", scale=1)
|
| 896 |
|
| 897 |
+
# Tabs
|
| 898 |
with gr.Tabs() as tabs:
|
| 899 |
+
# Tab 1: AI Conversation
|
| 900 |
+
with gr.Tab("💬 AI Conversation", id="chat_tab"):
|
|
|
|
| 901 |
reasoning_output = gr.HTML(
|
| 902 |
value=self.get_reasoning_html()
|
| 903 |
)
|
| 904 |
|
| 905 |
+
# Tab 2: Full Report
|
| 906 |
+
with gr.Tab("📊 Full Report", id="summary_tab", visible=False) as summary_tab:
|
| 907 |
+
gr.Markdown("*Team Agent complete analysis (plain text)*")
|
| 908 |
+
complete_summary_output = gr.Textbox(
|
| 909 |
+
show_label=False,
|
| 910 |
+
lines=20,
|
| 911 |
+
max_lines=25,
|
| 912 |
+
interactive=False
|
| 913 |
+
)
|
| 914 |
|
| 915 |
+
# Tab 3: Map
|
| 916 |
with gr.Tab("🗺️ Route Map", id="map_tab", visible=False) as map_tab:
|
|
|
|
| 917 |
map_output = gr.Plot(value=self.create_map(), show_label=False)
|
| 918 |
|
| 919 |
# Tab 4: Settings
|
| 920 |
with gr.Tab("⚙️ Settings", id="settings_tab"):
|
| 921 |
+
gr.Markdown("### 🔑 API Keys")
|
| 922 |
|
| 923 |
google_maps_key = gr.Textbox(
|
| 924 |
label="Google Maps API Key",
|
| 925 |
placeholder="AIzaSy...",
|
| 926 |
+
type="password"
|
|
|
|
| 927 |
)
|
| 928 |
|
| 929 |
openweather_key = gr.Textbox(
|
| 930 |
label="OpenWeather API Key",
|
| 931 |
+
type="password"
|
|
|
|
|
|
|
| 932 |
)
|
| 933 |
|
| 934 |
anthropic_key = gr.Textbox(
|
| 935 |
label="Anthropic API Key",
|
| 936 |
placeholder="sk-ant-...",
|
| 937 |
+
type="password"
|
|
|
|
| 938 |
)
|
| 939 |
|
| 940 |
+
gr.Markdown("### 🤖 Model")
|
| 941 |
|
| 942 |
model_choice = gr.Dropdown(
|
| 943 |
label="Select Model",
|
|
|
|
| 947 |
"gpt-4o",
|
| 948 |
"gemini-pro"
|
| 949 |
],
|
| 950 |
+
value="claude-sonnet-4-20250514"
|
| 951 |
)
|
| 952 |
|
| 953 |
+
save_settings_btn = gr.Button("💾 Save", variant="primary")
|
| 954 |
+
settings_status = gr.Textbox(label="Status", value="", interactive=False)
|
| 955 |
|
| 956 |
# Examples
|
| 957 |
gr.Examples(
|
| 958 |
examples=[
|
| 959 |
+
["Visit hospital tomorrow morning, buy groceries, mail package before 3pm", "Taipei", "08:30",
|
| 960 |
+
"15:00"]
|
| 961 |
],
|
| 962 |
inputs=[user_input, start_location, start_time, deadline]
|
| 963 |
)
|
| 964 |
|
| 965 |
+
# Event bindings
|
|
|
|
|
|
|
| 966 |
analyze_btn.click(
|
| 967 |
fn=self.step1_analyze_tasks,
|
| 968 |
inputs=[user_input],
|
|
|
|
| 981 |
outputs=[input_area, task_confirm_area]
|
| 982 |
)
|
| 983 |
|
|
|
|
| 984 |
back_btn.click(
|
| 985 |
fn=lambda: (
|
| 986 |
gr.update(visible=True),
|
|
|
|
| 991 |
outputs=[input_area, task_confirm_area, chat_modify_area, status_bar]
|
| 992 |
)
|
| 993 |
|
|
|
|
| 994 |
chat_send.click(
|
| 995 |
fn=self.modify_task_chat,
|
| 996 |
inputs=[chat_input, chatbot],
|
|
|
|
| 1009 |
outputs=[chat_input]
|
| 1010 |
)
|
| 1011 |
|
|
|
|
| 1012 |
confirm_btn.click(
|
| 1013 |
fn=lambda: (
|
| 1014 |
gr.update(visible=False),
|
|
|
|
| 1021 |
outputs=[
|
| 1022 |
*agent_displays,
|
| 1023 |
reasoning_output,
|
| 1024 |
+
trip_result_display,
|
| 1025 |
+
complete_summary_output,
|
| 1026 |
+
map_output,
|
| 1027 |
+
map_tab,
|
| 1028 |
+
summary_tab,
|
| 1029 |
status_bar
|
| 1030 |
]
|
| 1031 |
).then(
|
|
|
|
| 1033 |
outputs=[
|
| 1034 |
*agent_displays,
|
| 1035 |
reasoning_output,
|
| 1036 |
+
trip_result_display,
|
| 1037 |
+
complete_summary_output,
|
| 1038 |
map_output,
|
| 1039 |
map_tab,
|
| 1040 |
+
summary_tab,
|
| 1041 |
status_bar
|
| 1042 |
]
|
| 1043 |
).then(
|
| 1044 |
+
fn=lambda: (
|
| 1045 |
+
gr.update(visible=True),
|
| 1046 |
+
gr.update(selected="summary_tab")
|
|
|
|
|
|
|
| 1047 |
),
|
| 1048 |
+
outputs=[trip_result_area, tabs]
|
|
|
|
| 1049 |
)
|
| 1050 |
|
|
|
|
| 1051 |
save_settings_btn.click(
|
| 1052 |
fn=self.save_settings,
|
| 1053 |
inputs=[google_maps_key, openweather_key, anthropic_key, model_choice],
|
|
|
|
| 1062 |
demo = app.build_interface()
|
| 1063 |
demo.launch(
|
| 1064 |
server_name="0.0.0.0",
|
| 1065 |
+
server_port=7860, # HuggingFace default 7860
|
| 1066 |
+
share=False,
|
| 1067 |
show_error=True
|
| 1068 |
)
|
| 1069 |
|
| 1070 |
|
| 1071 |
if __name__ == "__main__":
|
| 1072 |
+
main()
|