Marco310 commited on
Commit
a328f28
·
1 Parent(s): fbcd46b

update app.py size

Browse files
Files changed (1) hide show
  1. app.py +678 -580
app.py CHANGED
@@ -1,8 +1,9 @@
1
  """
2
- LifeFlow AI - Demo
3
- Agent status dynamically updated (use tool, task completed, etc.)
4
- Automatically switches to the Trip Summary Tab (full report + map) after planning is complete
5
- Perfectly compatible with Light/Dark themes
 
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 Version v9"""
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 information
37
  self.agents_info = {
38
  "planner": {
39
  "name": "Planner",
40
  "avatar": "👔",
41
- "role": "Chief Planner",
42
  "color": "#4A90E2"
43
  },
44
  "scout": {
45
  "name": "Scout",
46
  "avatar": "🕵️",
47
- "role": "POI Investigation Expert",
48
  "color": "#50C878"
49
  },
50
  "optimizer": {
51
  "name": "Optimizer",
52
  "avatar": "🤖",
53
- "role": "Route Optimization Engine",
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 (theme adaptive, no progress bar)"""
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: 4px solid {agent['color']};
98
- border-radius: 8px;
99
- padding: 12px;
100
- margin: 8px 0;
101
- transition: all 0.3s ease;
102
  ">
103
- <div style="display: flex; align-items: center; gap: 10px;">
104
- <span style="font-size: 1.8em;">{agent['avatar']}</span>
105
- <div style="flex: 1;">
106
- <div style="font-size: 1.1em; font-weight: bold; color: var(--body-text-color);">
107
- {agent['name']}
108
- </div>
109
- <div style="font-size: 0.85em; color: var(--body-text-color); opacity: 0.8; margin-top: 2px;">
110
- {icon} {message}
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 to record"""
119
- agent_info = self.agents_info.get(agent, {})
120
- avatar = agent_info.get('avatar', '🤖')
121
- name = agent_info.get('name', agent)
122
-
123
- timestamp = datetime.now().strftime("%H:%M:%S")
124
-
125
- # Choose style based on type
126
- if msg_type == "tool":
127
- icon = "🔧"
128
- bg_color = "var(--color-accent-soft)"
129
- elif msg_type == "success":
130
- icon = "✅"
131
- bg_color = "var(--background-fill-secondary)"
132
- elif msg_type == "thinking":
133
- icon = "💭"
134
- bg_color = "var(--background-fill-secondary)"
135
- else:
136
- icon = "ℹ️"
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
- """Get HTML of all reasoning messages (scrollable)"""
156
  if not self.reasoning_messages:
157
  return """
158
- <div style="text-align: center; padding: 40px; opacity: 0.6; color: var(--body-text-color);">
159
- <p>AI conversation logs will be displayed here</p>
160
- </div>
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 id="reasoning-container" style="max-height: 600px; overflow-y: auto; padding: 10px;">
167
- {messages_html}
168
- </div>
169
- <script>
170
- // Auto-scroll to bottom
171
- var container = document.getElementById('reasoning-container');
172
- if (container) {{
173
- container.scrollTop = container.scrollHeight;
174
- }}
175
- </script>
176
- """
177
 
178
- def create_task_list_ui(self, tasks: List[Dict]):
179
- """Create task list UI (theme adaptive)"""
180
  if not tasks:
181
- return "<p style='color: var(--body-text-color); opacity: 0.6;'>No tasks yet</p>"
182
 
183
  priority_colors = {
184
- 'HIGH': '#ff4444',
185
- 'MEDIUM': '#FFA500',
186
- 'LOW': '#4A90E2'
187
  }
188
 
189
- html = f"""
190
- <div class="slide-in" style="background: var(--background-fill-secondary); border-radius: 12px; padding: 20px; margin: 15px 0;">
191
- <h3 style="color: var(--body-text-color); margin-top: 0;">📋 Identified Task List</h3>
192
- """
 
 
 
 
193
 
194
- for i, task in enumerate(tasks):
 
 
195
  priority = task.get('priority', 'MEDIUM')
196
- color = priority_colors.get(priority, '#4A90E2')
 
 
 
197
 
198
  html += f"""
199
- <div style="background: var(--background-fill-primary); border-left: 4px solid {color}; border-radius: 8px;
200
- padding: 15px; margin: 10px 0;">
201
- <div style="font-size: 1.2em; color: var(--body-text-color); font-weight: bold;">
202
- {i + 1}. {task['description']}
 
 
 
 
 
 
 
203
  </div>
204
- <div style="color: var(--body-text-color); opacity: 0.7; margin-top: 5px;">
205
- <span style="background: {color}; color: white; padding: 3px 10px; border-radius: 12px;
206
- font-size: 0.85em; margin-right: 10px;">
207
  {priority}
208
  </span>
209
- <span>📁 {task.get('category', 'Other')}</span>
210
- {f" | ⏰ {task.get('time_window', 'Anytime')}" if task.get('time_window') else ""}
211
  </div>
212
  </div>
213
  """
214
 
215
- html += f"""
216
- <div style="margin-top: 20px; padding: 15px; background: var(--color-accent-soft); border-radius: 8px;">
217
- <div style="font-size: 1.1em; color: var(--body-text-color); font-weight: bold;">📊 Overview</div>
218
- <div style="color: var(--body-text-color); opacity: 0.9; margin-top: 5px;">
219
- Total tasks: {len(tasks)} |
220
- High priority: {sum(1 for t in tasks if t.get('priority') == 'HIGH')} |
221
- Medium priority: {sum(1 for t in tasks if t.get('priority') == 'MEDIUM')} |
222
- Low priority: {sum(1 for t in tasks if t.get('priority') == 'LOW')}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
223
  </div>
224
  </div>
225
  </div>
226
  """
227
 
228
- return html
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
229
 
230
- def create_map(self, route_data: Optional[Dict] = None) -> go.Figure:
231
- """Create route map"""
232
  fig = go.Figure()
233
 
234
- if route_data and 'stops' in route_data:
235
- stops = route_data['stops']
236
-
237
- # Extract coordinates
238
- lats = [stop['location']['lat'] for stop in stops]
239
- lons = [stop['location']['lng'] for stop in stops]
240
- names = [f"{i + 1}. {stop['poi_name']}" for i, stop in enumerate(stops)]
241
-
242
- # Add route lines
243
- fig.add_trace(go.Scattermapbox(
244
- lat=lats,
245
- lon=lons,
246
- mode='markers+lines+text',
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="open-street-map",
268
- center=dict(lat=center_lat, lon=center_lon),
269
- zoom=12
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 user requirements and extract tasks"""
282
- self.reasoning_messages = [] # Clear previous messages
283
- self.task_list = []
284
- self.planning_completed = False
285
 
286
- # Planner Agent start analysis
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.3)
293
-
294
- # Extract tasks (simulated)
295
- if "hospital" in user_input.lower() or "doctor" in user_input.lower() or "醫院" in user_input or "看病" in user_input:
296
- self.task_list.append({
297
- 'description': 'Go to hospital for medical consultation',
298
- 'category': 'Medical',
299
- 'priority': 'HIGH',
300
- 'estimated_duration': 45,
301
- 'time_window': '08:00-12:00'
302
- })
303
-
304
- if "supermarket" in user_input.lower() or "shopping" in user_input.lower() or "grocery" in user_input.lower() or "超市" in user_input or "買菜" in user_input or "購物" in user_input:
305
- self.task_list.append({
306
- 'description': 'Go to supermarket for shopping',
307
- 'category': 'Shopping',
308
- 'priority': 'MEDIUM',
309
- 'estimated_duration': 30,
310
- 'time_window': 'Anytime'
311
- })
312
-
313
- if "post office" in user_input.lower() or "mail" in user_input.lower() or "郵局" in user_input or "寄包裹" in user_input:
314
- self.task_list.append({
315
- 'description': 'Go to post office to mail package',
316
- 'category': 'Postal',
317
- 'priority': 'HIGH',
318
- 'estimated_duration': 20,
319
- 'time_window': '09:00-15:00'
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", "Task analysis complete"),
355
- self.create_agent_card("scout", "idle", "Waiting for confirmation"),
356
- self.create_agent_card("optimizer", "idle", "Waiting for confirmation"),
357
- self.create_agent_card("validator", "idle", "Waiting for confirmation"),
358
- self.create_agent_card("weather", "idle", "Waiting for confirmation"),
359
- self.create_agent_card("traffic", "idle", "Waiting for confirmation")
360
  ]
361
 
 
 
 
362
  return (
363
  *agent_updates,
364
  self.get_reasoning_html(),
365
- task_list_html,
366
  task_summary,
367
- gr.update(visible=True), # Show task confirmation area
368
- gr.update(visible=True), # Show chat modification area
369
- [], # Clear chat history
370
- "Task analysis complete, please confirm or modify"
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 POI"""
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", "Task analysis complete ✓"),
430
- self.create_agent_card("scout", "working", "Searching for POIs..."),
431
- self.create_agent_card("optimizer", "waiting", "Waiting for search completion"),
432
- self.create_agent_card("validator", "waiting", "Waiting for search completion"),
433
- self.create_agent_card("weather", "idle", "Standby"),
434
- self.create_agent_card("traffic", "idle", "Standby")
435
  ]
436
 
437
- yield (
438
- *agent_updates,
439
- self.get_reasoning_html(),
440
- "Searching for POIs..."
441
- )
442
 
443
- time_module.sleep(0.5)
 
444
 
445
- # Simulate searching for each task
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
- self.poi_results.append(poi)
 
461
 
462
- self.add_reasoning_message("scout",
463
- f"Found POI: {poi['name']}<br>Rating: {poi['rating']}★ | Distance: {poi['distance']}m",
464
- "success")
465
 
466
- yield (
467
- *agent_updates,
468
- self.get_reasoning_html(),
469
- f"Found {i + 1}/{len(self.task_list)} POIs..."
470
- )
471
 
472
- # Search complete
473
- self.add_reasoning_message("scout",
474
- f"POI search complete! Found {len(self.poi_results)} suitable locations",
475
- "success")
476
 
477
- agent_updates[1] = self.create_agent_card("scout", "completed", "POI search complete ✓")
 
478
 
479
- yield (
480
- *agent_updates,
481
- self.get_reasoning_html(),
482
- "POI search complete"
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", "Task analysis complete ✓"),
492
- self.create_agent_card("scout", "completed", "POI search complete ✓"),
493
- self.create_agent_card("optimizer", "working", "Optimizing route..."),
494
- self.create_agent_card("validator", "waiting", "Waiting for optimization"),
495
- self.create_agent_card("weather", "idle", "Standby"),
496
- self.create_agent_card("traffic", "idle", "Standby")
497
  ]
498
 
499
- yield (
500
- *agent_updates,
501
- self.get_reasoning_html(),
502
- "", # trip_summary_display
503
- None, # map_output
504
- gr.update(visible=False), # map_tab
505
- "Optimizing route..."
506
- )
507
 
 
508
  time_module.sleep(0.5)
509
 
510
- # Call TSPTW solver
511
- self.add_reasoning_message("optimizer",
512
- "Using tool: optimize_route_with_time_windows()",
513
- "tool")
514
- time_module.sleep(0.8)
 
 
 
515
 
516
- # Generate simulated optimized route
517
- route_data = {
518
- 'stops': []
519
- }
520
 
521
- base_lat, base_lng = 25.0330, 121.5654
522
- for i, (task, poi) in enumerate(zip(self.task_list, self.poi_results)):
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("optimizer",
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
- agent_updates[2] = self.create_agent_card("optimizer", "completed", "Route optimization complete ✓")
543
- agent_updates[3] = self.create_agent_card("validator", "working", "Validating feasibility...")
544
 
545
- yield (
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
- # Validator validation
557
- self.add_reasoning_message("validator", "Starting feasibility validation...", "thinking")
558
- time_module.sleep(0.3)
 
 
 
 
 
559
 
560
- self.add_reasoning_message("validator",
561
- "Using tool: validate_feasibility()",
562
- "tool")
563
- time_module.sleep(0.5)
564
 
565
- self.add_reasoning_message("validator",
566
- "Validation complete! All constraints satisfied ✓<br>• Time window compliance: ✓<br>• Priority task completion: ✓<br>• Deadline met: ✓",
567
- "success")
568
 
569
- agent_updates[3] = self.create_agent_card("validator", "completed", "Feasibility validation complete ")
570
 
571
- # Generate complete trip summary
572
- trip_summary_html = self.create_trip_summary(route_data)
573
 
574
- # Generate map
575
- map_fig = self.create_map(route_data)
 
 
 
 
 
 
576
 
577
- self.planning_completed = True
 
578
 
579
- yield (
580
- *agent_updates,
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
- def create_trip_summary(self, route_data: Dict) -> str:
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
- <h3 style="color: var(--body-text-color);">📍 Detailed Itinerary</h3>
607
- """
 
 
 
 
 
 
608
 
609
- priority_colors = {
610
- 'HIGH': '#ff4444',
611
- 'MEDIUM': '#FFA500',
612
- 'LOW': '#4A90E2'
 
 
 
 
 
 
 
 
613
  }
614
 
615
- for stop in stops:
616
- color = priority_colors.get(stop.get('priority', 'MEDIUM'), '#4A90E2')
 
617
 
618
- html += f"""
619
- <div style="background: var(--background-fill-primary); border-left: 4px solid {color};
620
- border-radius: 8px; padding: 15px; margin: 10px 0;">
621
- <div style="display: flex; justify-content: space-between; align-items: start;">
622
- <div style="flex: 1;">
623
- <div style="font-size: 1.3em; font-weight: bold; color: var(--body-text-color);">
624
- {stop['order']}. {stop['poi_name']}
625
- </div>
626
- <div style="color: var(--body-text-color); opacity: 0.8; margin-top: 5px;">
627
- {stop['description']}
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
- <div style="color: var(--body-text-color); opacity: 0.7; margin-top: 10px;
639
- display: flex; gap: 15px; flex-wrap: wrap;">
640
- <span>🕐 Arrival: {stop['arrival_time']}</span>
641
- <span>🕐 Departure: {stop['departure_time']}</span>
642
- <span>⏱️ Duration: {stop['duration']} min</span>
643
- </div>
644
- </div>
645
- """
646
 
647
- html += """
648
- <div style="background: var(--color-accent-soft); border-radius: 8px; padding: 15px; margin-top: 20px;">
649
- <h3 style="color: var(--body-text-color); margin-top: 0;">✅ Validation Results</h3>
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
- return html
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 successfully"
669
 
670
  def build_interface(self):
671
- """Build Gradio interface"""
672
  with gr.Blocks(
673
- title="LifeFlow AI - Intelligent Trip Planning",
674
- theme=gr.themes.Soft(),
 
 
 
 
 
675
  css="""
676
- .breathing {
677
- animation: breathing 2s ease-in-out infinite;
678
- }
679
- @keyframes breathing {
680
- 0%, 100% { opacity: 1; }
681
- 50% { opacity: 0.7; }
682
- }
683
- .slide-in {
684
- animation: slideIn 0.5s ease-out;
685
- }
686
- @keyframes slideIn {
687
- from {
688
- opacity: 0;
689
- transform: translateY(-20px);
690
- }
691
- to {
692
- opacity: 1;
693
- transform: translateY(0);
694
- }
695
- }
696
- """
697
  ) as demo:
698
- # Title
699
- gr.HTML("""
700
- <div style="text-align: center; padding: 30px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
701
- border-radius: 15px; margin-bottom: 20px;">
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
- # ========== Left: Input and Team status ==========
714
- with gr.Column(scale=2, min_width=400):
715
- # Input area (initially visible)
716
  with gr.Group(visible=True) as input_area:
717
- gr.Markdown("### 📝 Enter Your Requirements")
718
 
719
  user_input = gr.Textbox(
720
- label="Trip Requirements",
721
- placeholder="E.g.: Tomorrow morning go to NTU Hospital, then go to PX Mart for shopping, need to go to post office to mail package before 3 PM",
722
- lines=4
723
  )
724
 
725
  with gr.Row():
726
  start_location = gr.Textbox(
727
- label="Starting Location",
728
- placeholder="E.g.: Daan District, Taipei",
729
  scale=2
730
  )
731
  start_time = gr.Textbox(
732
- label="Start Time",
733
  placeholder="08:30",
734
- value="08:30",
735
  scale=1
736
  )
737
 
738
  deadline = gr.Textbox(
739
- label="Deadline (Optional)",
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 area (initially hidden)
747
  with gr.Group(visible=False) as task_confirm_area:
748
  gr.Markdown("### ✅ Confirm Tasks")
749
 
750
- task_list_display = gr.HTML() # Task list
751
- task_summary_display = gr.HTML() # Task summary
752
 
753
  with gr.Row():
754
- back_btn = gr.Button(" Return to Modify", variant="secondary")
755
- confirm_btn = gr.Button("✅ Confirm Planning", variant="primary")
756
 
757
- # Team status area (initially hidden)
758
- with gr.Group(visible=False) as team_area:
759
- gr.Markdown("### 🤝 Team Collaboration Status")
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", "Standby"))
764
  agent_displays.append(agent_card)
765
 
766
- trip_summary_display = gr.HTML() # Trip summary (user-facing)
767
-
768
- # ========== Right: Status/Chat/Tabs ==========
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="📊 Real-time Status",
774
  value="Waiting for input...",
775
  interactive=False,
776
  max_lines=1
777
  )
778
 
779
- # Chat modification area (only shown during task confirmation)
780
  with gr.Group(visible=False) as chat_modify_area:
781
- gr.Markdown("### 💬 Chat with AI to Modify Tasks")
782
 
783
  chatbot = gr.Chatbot(
 
784
  value=[],
785
- height=150,
786
  show_label=False
787
  )
788
 
789
  with gr.Row():
790
  chat_input = gr.Textbox(
791
- placeholder="E.g.: Change task 2 to high priority",
792
  show_label=False,
793
  scale=4
794
  )
795
  chat_send = gr.Button("Send", variant="primary", scale=1)
796
 
797
- # Tabs area
798
  with gr.Tabs() as tabs:
799
- # Tab 1: AI conversation log (default)
800
- with gr.Tab("💬 AI Conversation Log", id="chat_tab"):
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: Complete report (shown after planning completes)
807
- with gr.Tab("📊 Complete Report", id="summary_tab", visible=False) as summary_tab:
808
- gr.Markdown("*Detailed trip planning report*")
809
- complete_summary_output = gr.HTML()
 
 
 
 
 
810
 
811
- # Tab 3: Route map (shown after planning completes)
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 Configuration")
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
- placeholder="...",
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 Settings")
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=self.settings['model']
852
  )
853
 
854
- save_settings_btn = gr.Button("💾 Save Settings", variant="primary")
855
- settings_status = gr.Textbox(label="Status", value="", interactive=False, max_lines=1)
856
 
857
  # Examples
858
  gr.Examples(
859
  examples=[
860
- ["Tomorrow morning go to NTU Hospital for consultation, then go to PX Mart for shopping, need to go to post office to mail package before 3 PM", "Daan District, Taipei", "08:30", "15:00"]
 
861
  ],
862
  inputs=[user_input, start_location, start_time, deadline]
863
  )
864
 
865
- # ========== Event binding ==========
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
- trip_summary_display,
 
937
  map_output,
938
  map_tab,
 
939
  status_bar
940
  ]
941
  ).then(
942
- # After planning completes, automatically switch to Complete Report Tab and display
943
- fn=lambda trip_summary: (
944
- trip_summary, # Complete report content
945
- gr.update(visible=True), # Show complete report tab
946
- gr.update(selected="summary_tab") # Automatically switch to complete report tab
947
  ),
948
- inputs=[trip_summary_display],
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=True,
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()