mashrur950 commited on
Commit
50e460a
Β·
1 Parent(s): 7ed6008

Enhance Gradio UI with new dashboard features for orders and drivers management

Browse files
Files changed (1) hide show
  1. ui/app.py +542 -329
ui/app.py CHANGED
@@ -1,6 +1,6 @@
1
  """
2
  FleetMind MCP - Gradio Web Interface
3
- Simple dashboard to interact with the MCP server and database
4
  """
5
 
6
  import sys
@@ -20,222 +20,363 @@ from chat.conversation import ConversationManager
20
  from chat.geocoding import GeocodingService
21
  import uuid
22
 
23
- # Global session storage (simple in-memory store)
24
  SESSIONS = {}
25
 
 
 
 
 
26
  # ============================================
27
- # DATABASE FUNCTIONS
28
  # ============================================
29
 
30
- def get_database_status():
31
- """Check if database is connected"""
32
  try:
33
- if test_connection():
34
- return "βœ… Connected", "success"
35
- else:
36
- return "❌ Disconnected", "error"
 
 
 
 
 
 
 
 
 
 
 
37
  except Exception as e:
38
- return f"❌ Error: {str(e)}", "error"
 
39
 
40
 
41
- def get_orders_summary():
42
- """Get summary of orders by status"""
43
  try:
44
  query = """
45
  SELECT
46
- status,
47
- COUNT(*) as count
48
- FROM orders
49
- GROUP BY status
50
- ORDER BY count DESC
 
51
  """
52
- results = execute_query(query)
 
 
 
 
 
 
53
 
54
- if not results:
55
- return "No orders in database"
56
 
57
- summary = "**Orders Summary:**\n\n"
58
- for row in results:
59
- summary += f"- {row['status'].upper()}: {row['count']}\n"
60
 
61
- return summary
62
- except Exception as e:
63
- return f"Error: {str(e)}"
 
 
64
 
 
 
 
65
 
66
- def get_all_orders():
67
- """Get all orders from database"""
68
- try:
69
- query = """
 
 
 
 
 
 
 
 
 
 
 
 
70
  SELECT
71
  order_id,
72
  customer_name,
73
  delivery_address,
74
  status,
75
  priority,
 
 
76
  created_at
77
  FROM orders
 
78
  ORDER BY created_at DESC
79
- LIMIT 50
80
  """
81
- results = execute_query(query)
 
82
 
83
  if not results:
84
- return [["No orders found", "", "", "", "", ""]]
85
 
86
- # Convert to list of lists for Gradio dataframe
87
  data = []
88
  for row in results:
 
 
 
 
 
 
 
 
 
 
 
89
  data.append([
90
  row['order_id'],
91
  row['customer_name'],
92
- row['delivery_address'][:50] + "..." if len(row['delivery_address']) > 50 else row['delivery_address'],
93
  row['status'],
94
  row['priority'],
95
- str(row['created_at'])
 
96
  ])
97
 
98
  return data
99
  except Exception as e:
100
- return [[f"Error: {str(e)}", "", "", "", "", ""]]
 
101
 
102
 
103
- def create_sample_order():
104
- """Create a sample order for testing"""
105
- try:
106
- now = datetime.now()
107
- order_id = f"ORD-{now.strftime('%Y%m%d%H%M%S')}"
108
 
 
109
  query = """
110
- INSERT INTO orders (
111
- order_id, customer_name, customer_phone, customer_email,
112
- delivery_address, delivery_lat, delivery_lng,
113
- time_window_start, time_window_end,
114
- priority, weight_kg, status
115
- ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
116
  """
 
117
 
118
- params = (
119
- order_id,
120
- "Sample Customer",
121
- "+1-555-0100",
122
123
- "456 Sample Street, San Francisco, CA 94103",
124
- 37.7749,
125
- -122.4194,
126
- now + timedelta(hours=2),
127
- now + timedelta(hours=6),
128
- "standard",
129
- 10.5,
130
- "pending"
131
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
 
133
- execute_write(query, params)
134
- return f"βœ… Order {order_id} created successfully!", get_all_orders()
135
  except Exception as e:
136
- return f"❌ Error: {str(e)}", get_all_orders()
137
 
138
 
139
- def search_orders(search_term):
140
- """Search orders by customer name or order ID"""
 
 
 
 
141
  try:
142
- if not search_term:
143
- return get_all_orders()
144
 
145
- query = """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
  SELECT
147
- order_id,
148
- customer_name,
149
- delivery_address,
150
  status,
151
- priority,
152
- created_at
153
- FROM orders
154
- WHERE
155
- order_id ILIKE %s OR
156
- customer_name ILIKE %s
157
- ORDER BY created_at DESC
158
- LIMIT 50
 
159
  """
160
 
161
- search_pattern = f"%{search_term}%"
162
- results = execute_query(query, (search_pattern, search_pattern))
163
 
164
  if not results:
165
- return [["No matching orders found", "", "", "", "", ""]]
166
 
 
167
  data = []
168
  for row in results:
 
 
 
 
 
 
 
 
 
169
  data.append([
170
- row['order_id'],
171
- row['customer_name'],
172
- row['delivery_address'][:50] + "..." if len(row['delivery_address']) > 50 else row['delivery_address'],
173
  row['status'],
174
- row['priority'],
175
- str(row['created_at'])
 
176
  ])
177
 
178
  return data
179
  except Exception as e:
180
- return [[f"Error: {str(e)}", "", "", "", "", ""]]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
181
 
182
 
183
  # ============================================
184
  # CHAT FUNCTIONS
185
  # ============================================
186
 
187
- # Initialize chat engine and geocoding service
188
- chat_engine = ChatEngine()
189
- geocoding_service = GeocodingService()
190
-
191
-
192
  def get_api_status():
193
  """Get API status for chat"""
194
- # Get full status for all providers
195
  full_status = chat_engine.get_full_status()
196
  selected = full_status["selected"]
197
  claude_status = full_status["claude"]["status"]
198
  gemini_status = full_status["gemini"]["status"]
199
-
200
  geocoding_status = geocoding_service.get_status()
201
 
202
- # Mark selected provider
203
  claude_marker = "🎯 **ACTIVE** - " if selected == "anthropic" else ""
204
  gemini_marker = "🎯 **ACTIVE** - " if selected == "gemini" else ""
205
 
206
- return f"""### API Status
207
-
208
- **AI Provider:**
209
-
210
- **Claude (Anthropic):**
211
- {claude_marker}{claude_status}
212
-
213
- **Gemini (Google):**
214
- {gemini_marker}{gemini_status}
215
-
216
- *πŸ’‘ Switch provider by setting `AI_PROVIDER=anthropic` or `AI_PROVIDER=gemini` in .env*
217
 
218
- ---
 
219
 
220
- **Geocoding:**
221
-
222
- **HERE Maps:**
223
- {geocoding_status}
224
  """
225
 
226
 
227
  def handle_chat_message(message, session_id):
228
- """
229
- Handle chat message from user
230
-
231
- Args:
232
- message: User's message
233
- session_id: Session identifier string
234
-
235
- Returns:
236
- Updated chatbot history, tool display, session_id
237
- """
238
- # Get or create conversation for this session
239
  if session_id not in SESSIONS:
240
  SESSIONS[session_id] = ConversationManager()
241
  welcome = chat_engine.get_welcome_message()
@@ -246,226 +387,101 @@ def handle_chat_message(message, session_id):
246
  if not message.strip():
247
  return conversation.get_formatted_history(), conversation.get_tool_calls(), session_id
248
 
249
- # Process message through chat engine
250
  response, tool_calls = chat_engine.process_message(message, conversation)
251
-
252
- # Return updated UI
253
  return conversation.get_formatted_history(), conversation.get_tool_calls(), session_id
254
 
255
 
256
  def reset_conversation(session_id):
257
  """Reset conversation to start fresh"""
258
- # Create new session ID for fresh conversation
259
  new_session_id = str(uuid.uuid4())
260
-
261
- # Create new conversation for this session
262
  new_conversation = ConversationManager()
263
-
264
- # Add welcome message
265
  welcome = chat_engine.get_welcome_message()
266
  new_conversation.add_message("assistant", welcome)
267
-
268
- # Store in sessions
269
  SESSIONS[new_session_id] = new_conversation
270
 
271
  return (
272
  new_conversation.get_formatted_history(),
273
- [], # Clear tool calls
274
  new_session_id
275
  )
276
 
277
 
278
  def get_initial_chat():
279
  """Get initial chat state with welcome message"""
280
- # Create new session ID
281
  session_id = str(uuid.uuid4())
282
-
283
- # Create conversation for this session
284
  conversation = ConversationManager()
285
  welcome = chat_engine.get_welcome_message()
286
  conversation.add_message("assistant", welcome)
287
-
288
- # Store in sessions
289
  SESSIONS[session_id] = conversation
290
 
291
  return conversation.get_formatted_history(), [], session_id
292
 
293
 
294
- # ============================================
295
- # MCP SERVER INFO
296
- # ============================================
297
-
298
- def get_mcp_server_info():
299
- """Get MCP server information"""
300
- mcp_info = {
301
- "server_name": "dispatch-coordinator-mcp",
302
- "version": "1.0.0",
303
- "status": "Ready",
304
- "tools": [
305
- "route_optimizer",
306
- "geocoder",
307
- "weather_monitor",
308
- "traffic_checker",
309
- "distance_matrix",
310
- "order_manager"
311
- ]
312
- }
313
-
314
- return f"""
315
- ### MCP Server Information
316
-
317
- **Name:** {mcp_info['server_name']}
318
- **Version:** {mcp_info['version']}
319
- **Status:** 🟒 {mcp_info['status']}
320
-
321
- **Available Tools ({len(mcp_info['tools'])}):**
322
- {chr(10).join([f"- {tool}" for tool in mcp_info['tools']])}
323
- """
324
-
325
-
326
  # ============================================
327
  # GRADIO INTERFACE
328
  # ============================================
329
 
330
  def create_interface():
331
- """Create the Gradio interface"""
332
 
333
- with gr.Blocks(theme=gr.themes.Soft(), title="FleetMind MCP Dashboard") as app:
334
 
335
- gr.Markdown("# 🚚 FleetMind MCP Dashboard")
336
- gr.Markdown("*Autonomous Dispatch Coordinator powered by MCP and PostgreSQL*")
337
 
338
  with gr.Tabs():
339
 
340
  # ==========================================
341
- # TAB 1: OVERVIEW
342
- # ==========================================
343
- with gr.Tab("πŸ“Š Overview"):
344
- with gr.Row():
345
- with gr.Column(scale=1):
346
- gr.Markdown("### System Status")
347
- db_status = gr.Textbox(
348
- label="Database Connection",
349
- value=get_database_status()[0],
350
- interactive=False
351
- )
352
- refresh_status_btn = gr.Button("πŸ”„ Refresh Status", size="sm")
353
-
354
- gr.Markdown("---")
355
- orders_summary = gr.Markdown(get_orders_summary())
356
-
357
- with gr.Column(scale=2):
358
- mcp_info = gr.Markdown(get_mcp_server_info())
359
-
360
- # Refresh status button action
361
- refresh_status_btn.click(
362
- fn=lambda: get_database_status()[0],
363
- outputs=db_status
364
- )
365
-
366
- # ==========================================
367
- # TAB 2: ORDERS MANAGEMENT
368
  # ==========================================
369
- with gr.Tab("πŸ“¦ Orders"):
370
- gr.Markdown("### Orders Management")
371
-
372
- with gr.Row():
373
- search_box = gr.Textbox(
374
- placeholder="Search by Order ID or Customer Name...",
375
- label="Search Orders",
376
- scale=3
377
- )
378
- search_btn = gr.Button("πŸ” Search", scale=1)
379
- create_btn = gr.Button("βž• Create Sample Order", scale=1, variant="primary")
380
-
381
- create_result = gr.Textbox(label="Result", visible=False)
382
-
383
- orders_table = gr.Dataframe(
384
- headers=["Order ID", "Customer", "Delivery Address", "Status", "Priority", "Created At"],
385
- datatype=["str", "str", "str", "str", "str", "str"],
386
- label="Orders List",
387
- value=get_all_orders(),
388
- interactive=False,
389
- wrap=True
390
- )
391
-
392
- refresh_orders_btn = gr.Button("πŸ”„ Refresh Orders")
393
-
394
- # Button actions
395
- create_btn.click(
396
- fn=create_sample_order,
397
- outputs=[create_result, orders_table]
398
- ).then(
399
- fn=lambda: gr.update(visible=True),
400
- outputs=create_result
401
- ).then(
402
- fn=lambda: get_orders_summary(),
403
- outputs=orders_summary
404
- )
405
-
406
- search_btn.click(
407
- fn=search_orders,
408
- inputs=search_box,
409
- outputs=orders_table
410
- )
411
-
412
- search_box.submit(
413
- fn=search_orders,
414
- inputs=search_box,
415
- outputs=orders_table
416
- )
417
-
418
- refresh_orders_btn.click(
419
- fn=get_all_orders,
420
- outputs=orders_table
421
- ).then(
422
- fn=lambda: get_orders_summary(),
423
- outputs=orders_summary
424
- )
425
-
426
- # ==========================================
427
- # TAB 3: AI CHAT
428
- # ==========================================
429
- with gr.Tab("πŸ’¬ Chat"):
430
  provider_name = chat_engine.get_provider_name()
431
  model_name = chat_engine.get_model_name()
432
 
433
- gr.Markdown(f"### AI Order Assistant")
434
- gr.Markdown(f"*Powered by: **{provider_name}** ({model_name})*")
435
 
436
- # API Status
437
- api_status = gr.Markdown(get_api_status())
 
 
 
 
 
438
 
439
  # Chat interface
440
  chatbot = gr.Chatbot(
441
- label="Order Assistant",
442
- height=500,
443
  type="messages",
444
- show_copy_button=True
 
445
  )
446
 
447
  msg_input = gr.Textbox(
448
- placeholder="e.g., 'Create an order for John Doe at 123 Main St, deliver by 5 PM'",
449
  label="Your Message",
450
- lines=2
451
  )
452
 
453
  with gr.Row():
454
- send_btn = gr.Button("πŸ“€ Send", variant="primary", scale=2)
455
- clear_btn = gr.Button("πŸ”„ Clear Chat", scale=1)
 
 
 
 
456
 
457
- # Tool usage display (reasoning transparency)
458
- with gr.Accordion("πŸ”§ Tool Usage (AI Reasoning)", open=False):
459
- gr.Markdown("See what tools the AI is using behind the scenes:")
460
- tool_display = gr.JSON(label="Tools Called")
461
 
462
- # Conversation state - use session ID instead of complex object
463
  session_id_state = gr.State(value=None)
464
 
465
  # Event handlers
466
  def send_message(message, sess_id):
467
- """Handle send button click"""
468
- # Auto-initialize session if None
469
  if sess_id is None:
470
  sess_id = str(uuid.uuid4())
471
  SESSIONS[sess_id] = ConversationManager()
@@ -473,7 +489,31 @@ def create_interface():
473
  SESSIONS[sess_id].add_message("assistant", welcome)
474
 
475
  chat_history, tools, new_sess_id = handle_chat_message(message, sess_id)
476
- return chat_history, tools, new_sess_id, "" # Clear input
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
477
 
478
  send_btn.click(
479
  fn=send_message,
@@ -494,56 +534,232 @@ def create_interface():
494
  )
495
 
496
  # ==========================================
497
- # TAB 4: MCP TOOLS (Coming Soon)
498
  # ==========================================
499
- with gr.Tab("πŸ”§ MCP Tools"):
500
- gr.Markdown("### MCP Tools")
501
- gr.Markdown("*MCP tool integration coming soon...*")
502
-
503
- gr.Markdown("""
504
- Available tools:
505
- - **route_optimizer** - Optimize delivery routes
506
- - **geocoder** - Convert addresses to coordinates
507
- - **weather_monitor** - Check weather conditions
508
- - **traffic_checker** - Monitor traffic conditions
509
- - **distance_matrix** - Calculate distances
510
- - **order_manager** - Manage orders via MCP
511
- """)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
512
 
513
  # ==========================================
514
- # TAB 5: DATABASE INFO
515
  # ==========================================
516
- with gr.Tab("πŸ’Ύ Database"):
517
- gr.Markdown("### Database Information")
518
-
519
- db_info = gr.Markdown(f"""
520
- **Database:** PostgreSQL
521
- **Name:** fleetmind
522
- **Host:** localhost
523
- **Port:** 5432
524
-
525
- **Tables:**
526
- - orders (26 columns)
527
- - drivers (coming soon)
528
- - assignments (coming soon)
529
- - exceptions (coming soon)
530
- """)
531
-
532
- test_db_btn = gr.Button("πŸ§ͺ Test Connection", variant="primary")
533
- test_result = gr.Textbox(label="Test Result", interactive=False)
534
-
535
- test_db_btn.click(
536
- fn=lambda: "βœ… Connection successful!" if test_connection() else "❌ Connection failed",
537
- outputs=test_result
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
538
  )
539
 
540
  gr.Markdown("---")
541
- gr.Markdown("*FleetMind MCP v1.0.0 - Built with Gradio, PostgreSQL, and FastMCP*")
542
 
543
- # Initialize chat on app load (deferred to avoid blocking)
544
  app.load(
545
  fn=get_initial_chat,
546
  outputs=[chatbot, tool_display, session_id_state]
 
 
 
 
 
 
547
  )
548
 
549
  return app
@@ -555,26 +771,23 @@ def create_interface():
555
 
556
  if __name__ == "__main__":
557
  print("=" * 60)
558
- print("FleetMind MCP - Starting Gradio Server")
559
  print("=" * 60)
560
 
561
- # Check database connection
562
  print("\nChecking database connection...")
563
  if test_connection():
564
  print("βœ… Database connected")
565
  else:
566
  print("❌ Database connection failed")
567
- print("Please check your .env file and PostgreSQL server")
568
 
569
  print("\nStarting Gradio interface...")
570
  print("=" * 60)
571
 
572
- # Create and launch the interface
573
  app = create_interface()
574
  app.launch(
575
- server_name="0.0.0.0", # Allow external connections for HF Spaces
576
  server_port=7860,
577
  share=False,
578
  show_error=True,
579
- show_api=False # Disable API docs to avoid schema parsing bug
580
  )
 
1
  """
2
  FleetMind MCP - Gradio Web Interface
3
+ Enhanced 3-tab dashboard: Chat, Orders, Drivers
4
  """
5
 
6
  import sys
 
20
  from chat.geocoding import GeocodingService
21
  import uuid
22
 
23
+ # Global session storage
24
  SESSIONS = {}
25
 
26
+ # Initialize chat engine and geocoding service
27
+ chat_engine = ChatEngine()
28
+ geocoding_service = GeocodingService()
29
+
30
  # ============================================
31
+ # STATISTICS FUNCTIONS
32
  # ============================================
33
 
34
+ def get_orders_stats():
35
+ """Get order statistics by status"""
36
  try:
37
+ query = """
38
+ SELECT
39
+ COUNT(*) as total,
40
+ COUNT(CASE WHEN status = 'pending' THEN 1 END) as pending,
41
+ COUNT(CASE WHEN status = 'assigned' THEN 1 END) as assigned,
42
+ COUNT(CASE WHEN status = 'in_transit' THEN 1 END) as in_transit,
43
+ COUNT(CASE WHEN status = 'delivered' THEN 1 END) as delivered,
44
+ COUNT(CASE WHEN status = 'failed' THEN 1 END) as failed,
45
+ COUNT(CASE WHEN status = 'cancelled' THEN 1 END) as cancelled
46
+ FROM orders
47
+ """
48
+ result = execute_query(query)
49
+ if result:
50
+ return result[0]
51
+ return {"total": 0, "pending": 0, "assigned": 0, "in_transit": 0, "delivered": 0, "failed": 0, "cancelled": 0}
52
  except Exception as e:
53
+ print(f"Error getting order stats: {e}")
54
+ return {"total": 0, "pending": 0, "assigned": 0, "in_transit": 0, "delivered": 0, "failed": 0, "cancelled": 0}
55
 
56
 
57
+ def get_drivers_stats():
58
+ """Get driver statistics by status"""
59
  try:
60
  query = """
61
  SELECT
62
+ COUNT(*) as total,
63
+ COUNT(CASE WHEN status = 'active' THEN 1 END) as active,
64
+ COUNT(CASE WHEN status = 'busy' THEN 1 END) as busy,
65
+ COUNT(CASE WHEN status = 'offline' THEN 1 END) as offline,
66
+ COUNT(CASE WHEN status = 'unavailable' THEN 1 END) as unavailable
67
+ FROM drivers
68
  """
69
+ result = execute_query(query)
70
+ if result:
71
+ return result[0]
72
+ return {"total": 0, "active": 0, "busy": 0, "offline": 0, "unavailable": 0}
73
+ except Exception as e:
74
+ print(f"Error getting driver stats: {e}")
75
+ return {"total": 0, "active": 0, "busy": 0, "offline": 0, "unavailable": 0}
76
 
 
 
77
 
78
+ # ============================================
79
+ # ORDERS FUNCTIONS
80
+ # ============================================
81
 
82
+ def get_all_orders(status_filter="all", priority_filter="all", payment_filter="all", search_term=""):
83
+ """Get orders with filters"""
84
+ try:
85
+ where_clauses = []
86
+ params = []
87
 
88
+ if status_filter and status_filter != "all":
89
+ where_clauses.append("status = %s")
90
+ params.append(status_filter)
91
 
92
+ if priority_filter and priority_filter != "all":
93
+ where_clauses.append("priority = %s")
94
+ params.append(priority_filter)
95
+
96
+ if payment_filter and payment_filter != "all":
97
+ where_clauses.append("payment_status = %s")
98
+ params.append(payment_filter)
99
+
100
+ if search_term:
101
+ where_clauses.append("(order_id ILIKE %s OR customer_name ILIKE %s OR customer_phone ILIKE %s)")
102
+ search_pattern = f"%{search_term}%"
103
+ params.extend([search_pattern, search_pattern, search_pattern])
104
+
105
+ where_sql = " WHERE " + " AND ".join(where_clauses) if where_clauses else ""
106
+
107
+ query = f"""
108
  SELECT
109
  order_id,
110
  customer_name,
111
  delivery_address,
112
  status,
113
  priority,
114
+ assigned_driver_id,
115
+ time_window_end,
116
  created_at
117
  FROM orders
118
+ {where_sql}
119
  ORDER BY created_at DESC
120
+ LIMIT 100
121
  """
122
+
123
+ results = execute_query(query, tuple(params) if params else ())
124
 
125
  if not results:
126
+ return [["-", "-", "-", "-", "-", "-", "-"]]
127
 
128
+ # Format data for table
129
  data = []
130
  for row in results:
131
+ # Truncate address if too long
132
+ address = row['delivery_address']
133
+ if len(address) > 40:
134
+ address = address[:37] + "..."
135
+
136
+ # Format deadline
137
+ deadline = row['time_window_end'].strftime("%Y-%m-%d %H:%M") if row['time_window_end'] else "No deadline"
138
+
139
+ # Driver ID or "Unassigned"
140
+ driver = row['assigned_driver_id'] if row['assigned_driver_id'] else "Unassigned"
141
+
142
  data.append([
143
  row['order_id'],
144
  row['customer_name'],
145
+ address,
146
  row['status'],
147
  row['priority'],
148
+ driver,
149
+ deadline
150
  ])
151
 
152
  return data
153
  except Exception as e:
154
+ print(f"Error fetching orders: {e}")
155
+ return [[f"Error: {str(e)}", "", "", "", "", "", ""]]
156
 
157
 
158
+ def get_order_details(order_id):
159
+ """Get complete order details"""
160
+ if not order_id or order_id == "-":
161
+ return "Select an order from the table to view details"
 
162
 
163
+ try:
164
  query = """
165
+ SELECT * FROM orders WHERE order_id = %s
 
 
 
 
 
166
  """
167
+ results = execute_query(query, (order_id,))
168
 
169
+ if not results:
170
+ return f"Order {order_id} not found"
171
+
172
+ order = results[0]
173
+
174
+ # Format the details nicely
175
+ details = f"""
176
+ # Order Details: {order_id}
177
+
178
+ ## Customer Information
179
+ - **Name:** {order['customer_name']}
180
+ - **Phone:** {order['customer_phone'] or 'N/A'}
181
+ - **Email:** {order['customer_email'] or 'N/A'}
182
+
183
+ ## Delivery Information
184
+ - **Address:** {order['delivery_address']}
185
+ - **Coordinates:** ({order['delivery_lat']}, {order['delivery_lng']})
186
+ - **Time Window:** {order['time_window_start']} to {order['time_window_end']}
187
+
188
+ ## Package Details
189
+ - **Weight:** {order['weight_kg'] or 'N/A'} kg
190
+ - **Volume:** {order['volume_m3'] or 'N/A'} mΒ³
191
+ - **Is Fragile:** {"Yes" if order['is_fragile'] else "No"}
192
+ - **Requires Signature:** {"Yes" if order['requires_signature'] else "No"}
193
+ - **Requires Cold Storage:** {"Yes" if order['requires_cold_storage'] else "No"}
194
+
195
+ ## Order Status
196
+ - **Status:** {order['status']}
197
+ - **Priority:** {order['priority']}
198
+ - **Assigned Driver:** {order['assigned_driver_id'] or 'Unassigned'}
199
+
200
+ ## Payment
201
+ - **Order Value:** ${order['order_value'] or '0.00'}
202
+ - **Payment Status:** {order['payment_status']}
203
+
204
+ ## Special Instructions
205
+ {order['special_instructions'] or 'None'}
206
+
207
+ ## Timestamps
208
+ - **Created:** {order['created_at']}
209
+ - **Updated:** {order['updated_at']}
210
+ - **Delivered:** {order['delivered_at'] or 'Not delivered yet'}
211
+ """
212
+ return details
213
 
 
 
214
  except Exception as e:
215
+ return f"Error fetching order details: {str(e)}"
216
 
217
 
218
+ # ============================================
219
+ # DRIVERS FUNCTIONS
220
+ # ============================================
221
+
222
+ def get_all_drivers(status_filter="all", vehicle_filter="all", search_term=""):
223
+ """Get drivers with filters"""
224
  try:
225
+ where_clauses = []
226
+ params = []
227
 
228
+ if status_filter and status_filter != "all":
229
+ where_clauses.append("status = %s")
230
+ params.append(status_filter)
231
+
232
+ if vehicle_filter and vehicle_filter != "all":
233
+ where_clauses.append("vehicle_type = %s")
234
+ params.append(vehicle_filter)
235
+
236
+ if search_term:
237
+ where_clauses.append("(driver_id ILIKE %s OR name ILIKE %s OR phone ILIKE %s OR vehicle_plate ILIKE %s)")
238
+ search_pattern = f"%{search_term}%"
239
+ params.extend([search_pattern, search_pattern, search_pattern, search_pattern])
240
+
241
+ where_sql = " WHERE " + " AND ".join(where_clauses) if where_clauses else ""
242
+
243
+ query = f"""
244
  SELECT
245
+ driver_id,
246
+ name,
247
+ phone,
248
  status,
249
+ vehicle_type,
250
+ vehicle_plate,
251
+ current_lat,
252
+ current_lng,
253
+ last_location_update
254
+ FROM drivers
255
+ {where_sql}
256
+ ORDER BY name ASC
257
+ LIMIT 100
258
  """
259
 
260
+ results = execute_query(query, tuple(params) if params else ())
 
261
 
262
  if not results:
263
+ return [["-", "-", "-", "-", "-", "-", "-"]]
264
 
265
+ # Format data for table
266
  data = []
267
  for row in results:
268
+ # Format location
269
+ if row['current_lat'] and row['current_lng']:
270
+ location = f"{row['current_lat']:.4f}, {row['current_lng']:.4f}"
271
+ else:
272
+ location = "No location"
273
+
274
+ # Format last update
275
+ last_update = row['last_location_update'].strftime("%Y-%m-%d %H:%M") if row['last_location_update'] else "Never"
276
+
277
  data.append([
278
+ row['driver_id'],
279
+ row['name'],
280
+ row['phone'] or "N/A",
281
  row['status'],
282
+ f"{row['vehicle_type']} - {row['vehicle_plate'] or 'N/A'}",
283
+ location,
284
+ last_update
285
  ])
286
 
287
  return data
288
  except Exception as e:
289
+ print(f"Error fetching drivers: {e}")
290
+ return [[f"Error: {str(e)}", "", "", "", "", "", ""]]
291
+
292
+
293
+ def get_driver_details(driver_id):
294
+ """Get complete driver details"""
295
+ if not driver_id or driver_id == "-":
296
+ return "Select a driver from the table to view details"
297
+
298
+ try:
299
+ query = """
300
+ SELECT * FROM drivers WHERE driver_id = %s
301
+ """
302
+ results = execute_query(query, (driver_id,))
303
+
304
+ if not results:
305
+ return f"Driver {driver_id} not found"
306
+
307
+ driver = results[0]
308
+
309
+ # Parse skills (handle both list and JSON string)
310
+ if driver['skills']:
311
+ if isinstance(driver['skills'], list):
312
+ skills = driver['skills']
313
+ else:
314
+ skills = json.loads(driver['skills'])
315
+ else:
316
+ skills = []
317
+ skills_str = ", ".join(skills) if skills else "None"
318
+
319
+ # Format the details nicely
320
+ details = f"""
321
+ # Driver Details: {driver_id}
322
+
323
+ ## Personal Information
324
+ - **Name:** {driver['name']}
325
+ - **Phone:** {driver['phone'] or 'N/A'}
326
+ - **Email:** {driver['email'] or 'N/A'}
327
+
328
+ ## Current Location
329
+ - **Coordinates:** ({driver['current_lat']}, {driver['current_lng']})
330
+ - **Last Update:** {driver['last_location_update'] or 'Never updated'}
331
+
332
+ ## Status
333
+ - **Status:** {driver['status']}
334
+
335
+ ## Vehicle Information
336
+ - **Type:** {driver['vehicle_type']}
337
+ - **Plate:** {driver['vehicle_plate'] or 'N/A'}
338
+ - **Capacity (kg):** {driver['capacity_kg'] or 'N/A'}
339
+ - **Capacity (mΒ³):** {driver['capacity_m3'] or 'N/A'}
340
+
341
+ ## Skills & Certifications
342
+ {skills_str}
343
+
344
+ ## Timestamps
345
+ - **Created:** {driver['created_at']}
346
+ - **Updated:** {driver['updated_at']}
347
+ """
348
+ return details
349
+
350
+ except Exception as e:
351
+ return f"Error fetching driver details: {str(e)}"
352
 
353
 
354
  # ============================================
355
  # CHAT FUNCTIONS
356
  # ============================================
357
 
 
 
 
 
 
358
  def get_api_status():
359
  """Get API status for chat"""
 
360
  full_status = chat_engine.get_full_status()
361
  selected = full_status["selected"]
362
  claude_status = full_status["claude"]["status"]
363
  gemini_status = full_status["gemini"]["status"]
 
364
  geocoding_status = geocoding_service.get_status()
365
 
 
366
  claude_marker = "🎯 **ACTIVE** - " if selected == "anthropic" else ""
367
  gemini_marker = "🎯 **ACTIVE** - " if selected == "gemini" else ""
368
 
369
+ return f"""**AI Provider:**
 
 
 
 
 
 
 
 
 
 
370
 
371
+ **Claude:** {claude_marker}{claude_status}
372
+ **Gemini:** {gemini_marker}{gemini_status}
373
 
374
+ **Geocoding:** {geocoding_status}
 
 
 
375
  """
376
 
377
 
378
  def handle_chat_message(message, session_id):
379
+ """Handle chat message from user"""
 
 
 
 
 
 
 
 
 
 
380
  if session_id not in SESSIONS:
381
  SESSIONS[session_id] = ConversationManager()
382
  welcome = chat_engine.get_welcome_message()
 
387
  if not message.strip():
388
  return conversation.get_formatted_history(), conversation.get_tool_calls(), session_id
389
 
 
390
  response, tool_calls = chat_engine.process_message(message, conversation)
 
 
391
  return conversation.get_formatted_history(), conversation.get_tool_calls(), session_id
392
 
393
 
394
  def reset_conversation(session_id):
395
  """Reset conversation to start fresh"""
 
396
  new_session_id = str(uuid.uuid4())
 
 
397
  new_conversation = ConversationManager()
 
 
398
  welcome = chat_engine.get_welcome_message()
399
  new_conversation.add_message("assistant", welcome)
 
 
400
  SESSIONS[new_session_id] = new_conversation
401
 
402
  return (
403
  new_conversation.get_formatted_history(),
404
+ [],
405
  new_session_id
406
  )
407
 
408
 
409
  def get_initial_chat():
410
  """Get initial chat state with welcome message"""
 
411
  session_id = str(uuid.uuid4())
 
 
412
  conversation = ConversationManager()
413
  welcome = chat_engine.get_welcome_message()
414
  conversation.add_message("assistant", welcome)
 
 
415
  SESSIONS[session_id] = conversation
416
 
417
  return conversation.get_formatted_history(), [], session_id
418
 
419
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
420
  # ============================================
421
  # GRADIO INTERFACE
422
  # ============================================
423
 
424
  def create_interface():
425
+ """Create the Gradio interface with 3 enhanced tabs"""
426
 
427
+ with gr.Blocks(theme=gr.themes.Soft(), title="FleetMind Dispatch System") as app:
428
 
429
+ gr.Markdown("# 🚚 FleetMind Dispatch System")
430
+ gr.Markdown("*AI-Powered Delivery Coordination*")
431
 
432
  with gr.Tabs():
433
 
434
  # ==========================================
435
+ # TAB 1: CHAT
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
436
  # ==========================================
437
+ with gr.Tab("πŸ’¬ Chat Assistant"):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
438
  provider_name = chat_engine.get_provider_name()
439
  model_name = chat_engine.get_model_name()
440
 
441
+ gr.Markdown(f"### AI Dispatch Assistant")
442
+ gr.Markdown(f"*Powered by {provider_name} ({model_name})*")
443
 
444
+ # Quick Action Buttons
445
+ gr.Markdown("**Quick Actions:**")
446
+ with gr.Row():
447
+ quick_create_order = gr.Button("πŸ“¦ Create Order", size="sm")
448
+ quick_view_orders = gr.Button("πŸ“‹ View Orders", size="sm")
449
+ quick_view_drivers = gr.Button("πŸ‘₯ View Drivers", size="sm")
450
+ quick_check_status = gr.Button("πŸ“Š Check Status", size="sm")
451
 
452
  # Chat interface
453
  chatbot = gr.Chatbot(
454
+ label="Chat with AI Assistant",
455
+ height=600,
456
  type="messages",
457
+ show_copy_button=True,
458
+ avatar_images=("πŸ‘€", "πŸ€–")
459
  )
460
 
461
  msg_input = gr.Textbox(
462
+ placeholder="Type your message here... (e.g., 'Create order for John at 123 Main St' or 'Show me available drivers')",
463
  label="Your Message",
464
+ lines=3
465
  )
466
 
467
  with gr.Row():
468
+ send_btn = gr.Button("πŸ“€ Send Message", variant="primary", scale=3)
469
+ clear_btn = gr.Button("πŸ—‘οΈ Clear Chat", scale=1)
470
+
471
+ # API Status in accordion
472
+ with gr.Accordion("πŸ”§ System Status", open=False):
473
+ api_status = gr.Markdown(get_api_status())
474
 
475
+ # Tool usage display
476
+ with gr.Accordion("πŸ› οΈ Tool Usage Log", open=False):
477
+ gr.Markdown("*See what tools the AI is using behind the scenes*")
478
+ tool_display = gr.JSON(label="Tools Called", value=[])
479
 
480
+ # Session state
481
  session_id_state = gr.State(value=None)
482
 
483
  # Event handlers
484
  def send_message(message, sess_id):
 
 
485
  if sess_id is None:
486
  sess_id = str(uuid.uuid4())
487
  SESSIONS[sess_id] = ConversationManager()
 
489
  SESSIONS[sess_id].add_message("assistant", welcome)
490
 
491
  chat_history, tools, new_sess_id = handle_chat_message(message, sess_id)
492
+ return chat_history, tools, new_sess_id, ""
493
+
494
+ # Quick action functions
495
+ def quick_action(prompt):
496
+ return prompt
497
+
498
+ quick_create_order.click(
499
+ fn=lambda: "Create a new order",
500
+ outputs=msg_input
501
+ )
502
+
503
+ quick_view_orders.click(
504
+ fn=lambda: "Show me all orders",
505
+ outputs=msg_input
506
+ )
507
+
508
+ quick_view_drivers.click(
509
+ fn=lambda: "Show me all available drivers",
510
+ outputs=msg_input
511
+ )
512
+
513
+ quick_check_status.click(
514
+ fn=lambda: "What is the current status of orders and drivers?",
515
+ outputs=msg_input
516
+ )
517
 
518
  send_btn.click(
519
  fn=send_message,
 
534
  )
535
 
536
  # ==========================================
537
+ # TAB 2: ORDERS
538
  # ==========================================
539
+ with gr.Tab("πŸ“¦ Orders Management"):
540
+ gr.Markdown("### Orders Dashboard")
541
+
542
+ # Statistics Cards
543
+ def update_order_stats():
544
+ stats = get_orders_stats()
545
+ return (
546
+ f"**Total:** {stats['total']}",
547
+ f"**Pending:** {stats['pending']}",
548
+ f"**In Transit:** {stats['in_transit']}",
549
+ f"**Delivered:** {stats['delivered']}"
550
+ )
551
+
552
+ with gr.Row():
553
+ stat_total = gr.Markdown("**Total:** 0")
554
+ stat_pending = gr.Markdown("**Pending:** 0")
555
+ stat_transit = gr.Markdown("**In Transit:** 0")
556
+ stat_delivered = gr.Markdown("**Delivered:** 0")
557
+
558
+ gr.Markdown("---")
559
+
560
+ # Filters
561
+ gr.Markdown("**Filters:**")
562
+ with gr.Row():
563
+ status_filter = gr.Dropdown(
564
+ choices=["all", "pending", "assigned", "in_transit", "delivered", "failed", "cancelled"],
565
+ value="all",
566
+ label="Status",
567
+ scale=1
568
+ )
569
+ priority_filter = gr.Dropdown(
570
+ choices=["all", "standard", "express", "urgent"],
571
+ value="all",
572
+ label="Priority",
573
+ scale=1
574
+ )
575
+ payment_filter = gr.Dropdown(
576
+ choices=["all", "pending", "paid", "cod"],
577
+ value="all",
578
+ label="Payment",
579
+ scale=1
580
+ )
581
+ search_orders = gr.Textbox(
582
+ placeholder="Search by Order ID, Customer, Phone...",
583
+ label="Search",
584
+ scale=2
585
+ )
586
+
587
+ with gr.Row():
588
+ apply_filters_btn = gr.Button("πŸ” Apply Filters", variant="primary", scale=1)
589
+ refresh_orders_btn = gr.Button("πŸ”„ Refresh", scale=1)
590
+
591
+ # Orders Table
592
+ orders_table = gr.Dataframe(
593
+ headers=["Order ID", "Customer", "Address", "Status", "Priority", "Driver", "Deadline"],
594
+ datatype=["str", "str", "str", "str", "str", "str", "str"],
595
+ label="Orders List (Click row to view details)",
596
+ value=get_all_orders(),
597
+ interactive=False,
598
+ wrap=True
599
+ )
600
+
601
+ # Order Details
602
+ gr.Markdown("---")
603
+ gr.Markdown("**Order Details:**")
604
+ order_details = gr.Markdown("*Select an order from the table above to view full details*")
605
+
606
+ # Event handlers
607
+ def filter_and_update_orders(status, priority, payment, search):
608
+ return get_all_orders(status, priority, payment, search)
609
+
610
+ def refresh_orders_and_stats(status, priority, payment, search):
611
+ stats = update_order_stats()
612
+ table = get_all_orders(status, priority, payment, search)
613
+ return stats[0], stats[1], stats[2], stats[3], table
614
+
615
+ def show_order_details(evt: gr.SelectData, table_data):
616
+ try:
617
+ # table_data is a pandas DataFrame from Gradio
618
+ if hasattr(table_data, 'iloc'):
619
+ # DataFrame - use iloc to access row and column
620
+ order_id = table_data.iloc[evt.index[0], 0]
621
+ else:
622
+ # List of lists - use standard indexing
623
+ order_id = table_data[evt.index[0]][0]
624
+ return get_order_details(order_id)
625
+ except Exception as e:
626
+ return f"Error: {str(e)}"
627
+
628
+ apply_filters_btn.click(
629
+ fn=filter_and_update_orders,
630
+ inputs=[status_filter, priority_filter, payment_filter, search_orders],
631
+ outputs=orders_table
632
+ )
633
+
634
+ refresh_orders_btn.click(
635
+ fn=refresh_orders_and_stats,
636
+ inputs=[status_filter, priority_filter, payment_filter, search_orders],
637
+ outputs=[stat_total, stat_pending, stat_transit, stat_delivered, orders_table]
638
+ )
639
+
640
+ orders_table.select(
641
+ fn=show_order_details,
642
+ inputs=[orders_table],
643
+ outputs=order_details
644
+ )
645
 
646
  # ==========================================
647
+ # TAB 3: DRIVERS
648
  # ==========================================
649
+ with gr.Tab("πŸ‘₯ Drivers Management"):
650
+ gr.Markdown("### Drivers Dashboard")
651
+
652
+ # Statistics Cards
653
+ def update_driver_stats():
654
+ stats = get_drivers_stats()
655
+ return (
656
+ f"**Total:** {stats['total']}",
657
+ f"**Active:** {stats['active']}",
658
+ f"**Busy:** {stats['busy']}",
659
+ f"**Offline:** {stats['offline']}"
660
+ )
661
+
662
+ with gr.Row():
663
+ driver_stat_total = gr.Markdown("**Total:** 0")
664
+ driver_stat_active = gr.Markdown("**Active:** 0")
665
+ driver_stat_busy = gr.Markdown("**Busy:** 0")
666
+ driver_stat_offline = gr.Markdown("**Offline:** 0")
667
+
668
+ gr.Markdown("---")
669
+
670
+ # Filters
671
+ gr.Markdown("**Filters:**")
672
+ with gr.Row():
673
+ driver_status_filter = gr.Dropdown(
674
+ choices=["all", "active", "busy", "offline", "unavailable"],
675
+ value="all",
676
+ label="Status",
677
+ scale=1
678
+ )
679
+ vehicle_filter = gr.Dropdown(
680
+ choices=["all", "van", "truck", "car", "motorcycle"],
681
+ value="all",
682
+ label="Vehicle Type",
683
+ scale=1
684
+ )
685
+ search_drivers = gr.Textbox(
686
+ placeholder="Search by Driver ID, Name, Phone, Plate...",
687
+ label="Search",
688
+ scale=2
689
+ )
690
+
691
+ with gr.Row():
692
+ apply_driver_filters_btn = gr.Button("πŸ” Apply Filters", variant="primary", scale=1)
693
+ refresh_drivers_btn = gr.Button("πŸ”„ Refresh", scale=1)
694
+
695
+ # Drivers Table
696
+ drivers_table = gr.Dataframe(
697
+ headers=["Driver ID", "Name", "Phone", "Status", "Vehicle", "Location", "Last Update"],
698
+ datatype=["str", "str", "str", "str", "str", "str", "str"],
699
+ label="Drivers List (Click row to view details)",
700
+ value=get_all_drivers(),
701
+ interactive=False,
702
+ wrap=True
703
+ )
704
+
705
+ # Driver Details
706
+ gr.Markdown("---")
707
+ gr.Markdown("**Driver Details:**")
708
+ driver_details = gr.Markdown("*Select a driver from the table above to view full details*")
709
+
710
+ # Event handlers
711
+ def filter_and_update_drivers(status, vehicle, search):
712
+ return get_all_drivers(status, vehicle, search)
713
+
714
+ def refresh_drivers_and_stats(status, vehicle, search):
715
+ stats = update_driver_stats()
716
+ table = get_all_drivers(status, vehicle, search)
717
+ return stats[0], stats[1], stats[2], stats[3], table
718
+
719
+ def show_driver_details(evt: gr.SelectData, table_data):
720
+ try:
721
+ # table_data is a pandas DataFrame from Gradio
722
+ if hasattr(table_data, 'iloc'):
723
+ # DataFrame - use iloc to access row and column
724
+ driver_id = table_data.iloc[evt.index[0], 0]
725
+ else:
726
+ # List of lists - use standard indexing
727
+ driver_id = table_data[evt.index[0]][0]
728
+ return get_driver_details(driver_id)
729
+ except Exception as e:
730
+ return f"Error: {str(e)}"
731
+
732
+ apply_driver_filters_btn.click(
733
+ fn=filter_and_update_drivers,
734
+ inputs=[driver_status_filter, vehicle_filter, search_drivers],
735
+ outputs=drivers_table
736
+ )
737
+
738
+ refresh_drivers_btn.click(
739
+ fn=refresh_drivers_and_stats,
740
+ inputs=[driver_status_filter, vehicle_filter, search_drivers],
741
+ outputs=[driver_stat_total, driver_stat_active, driver_stat_busy, driver_stat_offline, drivers_table]
742
+ )
743
+
744
+ drivers_table.select(
745
+ fn=show_driver_details,
746
+ inputs=[drivers_table],
747
+ outputs=driver_details
748
  )
749
 
750
  gr.Markdown("---")
751
+ gr.Markdown("*FleetMind v1.0 - AI-Powered Dispatch Coordination*")
752
 
753
+ # Initialize chat and stats on load
754
  app.load(
755
  fn=get_initial_chat,
756
  outputs=[chatbot, tool_display, session_id_state]
757
+ ).then(
758
+ fn=update_order_stats,
759
+ outputs=[stat_total, stat_pending, stat_transit, stat_delivered]
760
+ ).then(
761
+ fn=update_driver_stats,
762
+ outputs=[driver_stat_total, driver_stat_active, driver_stat_busy, driver_stat_offline]
763
  )
764
 
765
  return app
 
771
 
772
  if __name__ == "__main__":
773
  print("=" * 60)
774
+ print("FleetMind - Starting Enhanced UI")
775
  print("=" * 60)
776
 
 
777
  print("\nChecking database connection...")
778
  if test_connection():
779
  print("βœ… Database connected")
780
  else:
781
  print("❌ Database connection failed")
 
782
 
783
  print("\nStarting Gradio interface...")
784
  print("=" * 60)
785
 
 
786
  app = create_interface()
787
  app.launch(
788
+ server_name="0.0.0.0",
789
  server_port=7860,
790
  share=False,
791
  show_error=True,
792
+ show_api=False
793
  )