Marco310 commited on
Commit
fbcd46b
ยท
1 Parent(s): ac130eb

update demo setting

Browse files
Files changed (2) hide show
  1. README.md +1 -1
  2. app.py +974 -0
README.md CHANGED
@@ -5,7 +5,7 @@ colorFrom: blue
5
  colorTo: green
6
  sdk: gradio
7
  sdk_version: 5.49.1
8
- app_file: prototypes/app_demo.py
9
  pinned: false
10
  license: mit
11
  short_description: Your journey, in perfect rhythm
 
5
  colorTo: green
6
  sdk: gradio
7
  sdk_version: 5.49.1
8
+ app_file: app.py
9
  pinned: false
10
  license: mit
11
  short_description: Your journey, in perfect rhythm
app.py ADDED
@@ -0,0 +1,974 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
9
+ import plotly.graph_objects as go
10
+ from datetime import datetime, time
11
+ import json
12
+ from typing import Dict, List, Optional, Tuple
13
+ 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
21
+ self.current_step = 0
22
+ self.task_list = []
23
+ self.poi_results = []
24
+ self.chat_history = []
25
+ self.reasoning_messages = []
26
+ self.planning_completed = False
27
+
28
+ # Settings
29
+ self.settings = {
30
+ 'google_maps_api_key': '',
31
+ 'openweather_api_key': '',
32
+ 'anthropic_api_key': '',
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": {
57
+ "name": "Validator",
58
+ "avatar": "๐Ÿ›ก๏ธ",
59
+ "role": "Quality Assurance Expert",
60
+ "color": "#7ED321"
61
+ },
62
+ "weather": {
63
+ "name": "Weather",
64
+ "avatar": "๐ŸŒˆ",
65
+ "role": "Weather Analyst",
66
+ "color": "#50E3C2"
67
+ },
68
+ "traffic": {
69
+ "name": "Traffic",
70
+ "avatar": "๐Ÿš—",
71
+ "role": "Traffic Planner",
72
+ "color": "#FF6B6B"
73
+ }
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 = {
81
+ "idle": "โšช",
82
+ "thinking": "๐Ÿ’ญ",
83
+ "using_tool": "๐Ÿ”ง",
84
+ "working": "โš™๏ธ",
85
+ "completed": "โœ…",
86
+ "waiting": "โธ๏ธ",
87
+ "error": "โŒ"
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",
845
+ choices=[
846
+ "claude-sonnet-4-20250514",
847
+ "claude-opus-4-20250514",
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],
871
+ outputs=[
872
+ *agent_displays,
873
+ reasoning_output,
874
+ task_list_display,
875
+ task_summary_display,
876
+ task_confirm_area,
877
+ chat_modify_area,
878
+ chatbot,
879
+ status_bar
880
+ ]
881
+ ).then(
882
+ fn=lambda: (gr.update(visible=False), gr.update(visible=True)),
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),
890
+ gr.update(visible=False),
891
+ gr.update(visible=False),
892
+ "Waiting for input..."
893
+ ),
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],
901
+ outputs=[chatbot, task_list_display]
902
+ ).then(
903
+ fn=lambda: "",
904
+ outputs=[chat_input]
905
+ )
906
+
907
+ chat_input.submit(
908
+ fn=self.modify_task_chat,
909
+ inputs=[chat_input, chatbot],
910
+ outputs=[chatbot, task_list_display]
911
+ ).then(
912
+ fn=lambda: "",
913
+ outputs=[chat_input]
914
+ )
915
+
916
+ # Confirm planning
917
+ confirm_btn.click(
918
+ fn=lambda: (
919
+ gr.update(visible=False),
920
+ gr.update(visible=False),
921
+ gr.update(visible=True)
922
+ ),
923
+ outputs=[task_confirm_area, chat_modify_area, team_area]
924
+ ).then(
925
+ fn=self.step2_search_pois,
926
+ outputs=[
927
+ *agent_displays,
928
+ reasoning_output,
929
+ status_bar
930
+ ]
931
+ ).then(
932
+ fn=self.step3_optimize_route,
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],
956
+ outputs=[settings_status]
957
+ )
958
+
959
+ return demo
960
+
961
+
962
+ def main():
963
+ app = LifeFlowInteractive()
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()