|
|
""" |
|
|
FleetMind MCP - Gradio Web Interface |
|
|
Simple dashboard to interact with the MCP server and database |
|
|
""" |
|
|
|
|
|
import sys |
|
|
from pathlib import Path |
|
|
|
|
|
|
|
|
sys.path.insert(0, str(Path(__file__).parent.parent)) |
|
|
|
|
|
import gradio as gr |
|
|
from database.connection import execute_query, execute_write, test_connection |
|
|
from datetime import datetime, timedelta |
|
|
import json |
|
|
|
|
|
|
|
|
from chat.chat_engine import ChatEngine |
|
|
from chat.conversation import ConversationManager |
|
|
from chat.geocoding import GeocodingService |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_database_status(): |
|
|
"""Check if database is connected""" |
|
|
try: |
|
|
if test_connection(): |
|
|
return "β
Connected", "success" |
|
|
else: |
|
|
return "β Disconnected", "error" |
|
|
except Exception as e: |
|
|
return f"β Error: {str(e)}", "error" |
|
|
|
|
|
|
|
|
def get_orders_summary(): |
|
|
"""Get summary of orders by status""" |
|
|
try: |
|
|
query = """ |
|
|
SELECT |
|
|
status, |
|
|
COUNT(*) as count |
|
|
FROM orders |
|
|
GROUP BY status |
|
|
ORDER BY count DESC |
|
|
""" |
|
|
results = execute_query(query) |
|
|
|
|
|
if not results: |
|
|
return "No orders in database" |
|
|
|
|
|
summary = "**Orders Summary:**\n\n" |
|
|
for row in results: |
|
|
summary += f"- {row['status'].upper()}: {row['count']}\n" |
|
|
|
|
|
return summary |
|
|
except Exception as e: |
|
|
return f"Error: {str(e)}" |
|
|
|
|
|
|
|
|
def get_all_orders(): |
|
|
"""Get all orders from database""" |
|
|
try: |
|
|
query = """ |
|
|
SELECT |
|
|
order_id, |
|
|
customer_name, |
|
|
delivery_address, |
|
|
status, |
|
|
priority, |
|
|
created_at |
|
|
FROM orders |
|
|
ORDER BY created_at DESC |
|
|
LIMIT 50 |
|
|
""" |
|
|
results = execute_query(query) |
|
|
|
|
|
if not results: |
|
|
return [["No orders found", "", "", "", "", ""]] |
|
|
|
|
|
|
|
|
data = [] |
|
|
for row in results: |
|
|
data.append([ |
|
|
row['order_id'], |
|
|
row['customer_name'], |
|
|
row['delivery_address'][:50] + "..." if len(row['delivery_address']) > 50 else row['delivery_address'], |
|
|
row['status'], |
|
|
row['priority'], |
|
|
str(row['created_at']) |
|
|
]) |
|
|
|
|
|
return data |
|
|
except Exception as e: |
|
|
return [[f"Error: {str(e)}", "", "", "", "", ""]] |
|
|
|
|
|
|
|
|
def create_sample_order(): |
|
|
"""Create a sample order for testing""" |
|
|
try: |
|
|
now = datetime.now() |
|
|
order_id = f"ORD-{now.strftime('%Y%m%d%H%M%S')}" |
|
|
|
|
|
query = """ |
|
|
INSERT INTO orders ( |
|
|
order_id, customer_name, customer_phone, customer_email, |
|
|
delivery_address, delivery_lat, delivery_lng, |
|
|
time_window_start, time_window_end, |
|
|
priority, weight_kg, status |
|
|
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) |
|
|
""" |
|
|
|
|
|
params = ( |
|
|
order_id, |
|
|
"Sample Customer", |
|
|
"+1-555-0100", |
|
|
"[email protected]", |
|
|
"456 Sample Street, San Francisco, CA 94103", |
|
|
37.7749, |
|
|
-122.4194, |
|
|
now + timedelta(hours=2), |
|
|
now + timedelta(hours=6), |
|
|
"standard", |
|
|
10.5, |
|
|
"pending" |
|
|
) |
|
|
|
|
|
execute_write(query, params) |
|
|
return f"β
Order {order_id} created successfully!", get_all_orders() |
|
|
except Exception as e: |
|
|
return f"β Error: {str(e)}", get_all_orders() |
|
|
|
|
|
|
|
|
def search_orders(search_term): |
|
|
"""Search orders by customer name or order ID""" |
|
|
try: |
|
|
if not search_term: |
|
|
return get_all_orders() |
|
|
|
|
|
query = """ |
|
|
SELECT |
|
|
order_id, |
|
|
customer_name, |
|
|
delivery_address, |
|
|
status, |
|
|
priority, |
|
|
created_at |
|
|
FROM orders |
|
|
WHERE |
|
|
order_id ILIKE %s OR |
|
|
customer_name ILIKE %s |
|
|
ORDER BY created_at DESC |
|
|
LIMIT 50 |
|
|
""" |
|
|
|
|
|
search_pattern = f"%{search_term}%" |
|
|
results = execute_query(query, (search_pattern, search_pattern)) |
|
|
|
|
|
if not results: |
|
|
return [["No matching orders found", "", "", "", "", ""]] |
|
|
|
|
|
data = [] |
|
|
for row in results: |
|
|
data.append([ |
|
|
row['order_id'], |
|
|
row['customer_name'], |
|
|
row['delivery_address'][:50] + "..." if len(row['delivery_address']) > 50 else row['delivery_address'], |
|
|
row['status'], |
|
|
row['priority'], |
|
|
str(row['created_at']) |
|
|
]) |
|
|
|
|
|
return data |
|
|
except Exception as e: |
|
|
return [[f"Error: {str(e)}", "", "", "", "", ""]] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
chat_engine = ChatEngine() |
|
|
geocoding_service = GeocodingService() |
|
|
|
|
|
|
|
|
def get_api_status(): |
|
|
"""Get API status for chat""" |
|
|
|
|
|
full_status = chat_engine.get_full_status() |
|
|
selected = full_status["selected"] |
|
|
claude_status = full_status["claude"]["status"] |
|
|
gemini_status = full_status["gemini"]["status"] |
|
|
|
|
|
geocoding_status = geocoding_service.get_status() |
|
|
|
|
|
|
|
|
claude_marker = "π― **ACTIVE** - " if selected == "anthropic" else "" |
|
|
gemini_marker = "π― **ACTIVE** - " if selected == "gemini" else "" |
|
|
|
|
|
return f"""### API Status |
|
|
|
|
|
**AI Provider:** |
|
|
|
|
|
**Claude (Anthropic):** |
|
|
{claude_marker}{claude_status} |
|
|
|
|
|
**Gemini (Google):** |
|
|
{gemini_marker}{gemini_status} |
|
|
|
|
|
*π‘ Switch provider by setting `AI_PROVIDER=anthropic` or `AI_PROVIDER=gemini` in .env* |
|
|
|
|
|
--- |
|
|
|
|
|
**Geocoding:** |
|
|
|
|
|
**HERE Maps:** |
|
|
{geocoding_status} |
|
|
""" |
|
|
|
|
|
|
|
|
def handle_chat_message(message, conversation_state): |
|
|
""" |
|
|
Handle chat message from user |
|
|
|
|
|
Args: |
|
|
message: User's message |
|
|
conversation_state: ConversationManager instance |
|
|
|
|
|
Returns: |
|
|
Updated chatbot history, tool display, conversation state |
|
|
""" |
|
|
if not message.strip(): |
|
|
return conversation_state.get_formatted_history(), conversation_state.get_tool_calls(), conversation_state |
|
|
|
|
|
|
|
|
response, tool_calls = chat_engine.process_message(message, conversation_state) |
|
|
|
|
|
|
|
|
return conversation_state.get_formatted_history(), conversation_state.get_tool_calls(), conversation_state |
|
|
|
|
|
|
|
|
def reset_conversation(): |
|
|
"""Reset conversation to start fresh""" |
|
|
new_conversation = ConversationManager() |
|
|
|
|
|
|
|
|
welcome = chat_engine.get_welcome_message() |
|
|
new_conversation.add_message("assistant", welcome) |
|
|
|
|
|
return ( |
|
|
new_conversation.get_formatted_history(), |
|
|
[], |
|
|
new_conversation |
|
|
) |
|
|
|
|
|
|
|
|
def get_initial_chat(): |
|
|
"""Get initial chat state with welcome message""" |
|
|
conversation = ConversationManager() |
|
|
welcome = chat_engine.get_welcome_message() |
|
|
conversation.add_message("assistant", welcome) |
|
|
|
|
|
return conversation.get_formatted_history(), [], conversation |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_mcp_server_info(): |
|
|
"""Get MCP server information""" |
|
|
mcp_info = { |
|
|
"server_name": "dispatch-coordinator-mcp", |
|
|
"version": "1.0.0", |
|
|
"status": "Ready", |
|
|
"tools": [ |
|
|
"route_optimizer", |
|
|
"geocoder", |
|
|
"weather_monitor", |
|
|
"traffic_checker", |
|
|
"distance_matrix", |
|
|
"order_manager" |
|
|
] |
|
|
} |
|
|
|
|
|
return f""" |
|
|
### MCP Server Information |
|
|
|
|
|
**Name:** {mcp_info['server_name']} |
|
|
**Version:** {mcp_info['version']} |
|
|
**Status:** π’ {mcp_info['status']} |
|
|
|
|
|
**Available Tools ({len(mcp_info['tools'])}):** |
|
|
{chr(10).join([f"- {tool}" for tool in mcp_info['tools']])} |
|
|
""" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def create_interface(): |
|
|
"""Create the Gradio interface""" |
|
|
|
|
|
with gr.Blocks(theme=gr.themes.Soft(), title="FleetMind MCP Dashboard") as app: |
|
|
|
|
|
gr.Markdown("# π FleetMind MCP Dashboard") |
|
|
gr.Markdown("*Autonomous Dispatch Coordinator powered by MCP and PostgreSQL*") |
|
|
|
|
|
with gr.Tabs(): |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
with gr.Tab("π Overview"): |
|
|
with gr.Row(): |
|
|
with gr.Column(scale=1): |
|
|
gr.Markdown("### System Status") |
|
|
db_status = gr.Textbox( |
|
|
label="Database Connection", |
|
|
value=get_database_status()[0], |
|
|
interactive=False |
|
|
) |
|
|
refresh_status_btn = gr.Button("π Refresh Status", size="sm") |
|
|
|
|
|
gr.Markdown("---") |
|
|
orders_summary = gr.Markdown(get_orders_summary()) |
|
|
|
|
|
with gr.Column(scale=2): |
|
|
mcp_info = gr.Markdown(get_mcp_server_info()) |
|
|
|
|
|
|
|
|
refresh_status_btn.click( |
|
|
fn=lambda: get_database_status()[0], |
|
|
outputs=db_status |
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
with gr.Tab("π¦ Orders"): |
|
|
gr.Markdown("### Orders Management") |
|
|
|
|
|
with gr.Row(): |
|
|
search_box = gr.Textbox( |
|
|
placeholder="Search by Order ID or Customer Name...", |
|
|
label="Search Orders", |
|
|
scale=3 |
|
|
) |
|
|
search_btn = gr.Button("π Search", scale=1) |
|
|
create_btn = gr.Button("β Create Sample Order", scale=1, variant="primary") |
|
|
|
|
|
create_result = gr.Textbox(label="Result", visible=False) |
|
|
|
|
|
orders_table = gr.Dataframe( |
|
|
headers=["Order ID", "Customer", "Delivery Address", "Status", "Priority", "Created At"], |
|
|
datatype=["str", "str", "str", "str", "str", "str"], |
|
|
label="Orders List", |
|
|
value=get_all_orders(), |
|
|
interactive=False, |
|
|
wrap=True |
|
|
) |
|
|
|
|
|
refresh_orders_btn = gr.Button("π Refresh Orders") |
|
|
|
|
|
|
|
|
create_btn.click( |
|
|
fn=create_sample_order, |
|
|
outputs=[create_result, orders_table] |
|
|
).then( |
|
|
fn=lambda: gr.update(visible=True), |
|
|
outputs=create_result |
|
|
).then( |
|
|
fn=lambda: get_orders_summary(), |
|
|
outputs=orders_summary |
|
|
) |
|
|
|
|
|
search_btn.click( |
|
|
fn=search_orders, |
|
|
inputs=search_box, |
|
|
outputs=orders_table |
|
|
) |
|
|
|
|
|
search_box.submit( |
|
|
fn=search_orders, |
|
|
inputs=search_box, |
|
|
outputs=orders_table |
|
|
) |
|
|
|
|
|
refresh_orders_btn.click( |
|
|
fn=get_all_orders, |
|
|
outputs=orders_table |
|
|
).then( |
|
|
fn=lambda: get_orders_summary(), |
|
|
outputs=orders_summary |
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
with gr.Tab("π¬ Chat"): |
|
|
provider_name = chat_engine.get_provider_name() |
|
|
model_name = chat_engine.get_model_name() |
|
|
|
|
|
gr.Markdown(f"### AI Order Assistant") |
|
|
gr.Markdown(f"*Powered by: **{provider_name}** ({model_name})*") |
|
|
|
|
|
|
|
|
api_status = gr.Markdown(get_api_status()) |
|
|
|
|
|
|
|
|
chatbot = gr.Chatbot( |
|
|
label="Order Assistant", |
|
|
height=500, |
|
|
type="messages", |
|
|
show_copy_button=True |
|
|
) |
|
|
|
|
|
msg_input = gr.Textbox( |
|
|
placeholder="e.g., 'Create an order for John Doe at 123 Main St, deliver by 5 PM'", |
|
|
label="Your Message", |
|
|
lines=2 |
|
|
) |
|
|
|
|
|
with gr.Row(): |
|
|
send_btn = gr.Button("π€ Send", variant="primary", scale=2) |
|
|
clear_btn = gr.Button("π Clear Chat", scale=1) |
|
|
|
|
|
|
|
|
with gr.Accordion("π§ Tool Usage (AI Reasoning)", open=False): |
|
|
gr.Markdown("See what tools the AI is using behind the scenes:") |
|
|
tool_display = gr.JSON(label="Tools Called") |
|
|
|
|
|
|
|
|
conversation_state = gr.State(value=None) |
|
|
|
|
|
|
|
|
chatbot.value, tool_display.value, conversation_state.value = get_initial_chat() |
|
|
|
|
|
|
|
|
def send_message(message, conv_state): |
|
|
"""Handle send button click""" |
|
|
chat_history, tools, new_state = handle_chat_message(message, conv_state) |
|
|
return chat_history, tools, new_state, "" |
|
|
|
|
|
send_btn.click( |
|
|
fn=send_message, |
|
|
inputs=[msg_input, conversation_state], |
|
|
outputs=[chatbot, tool_display, conversation_state, msg_input] |
|
|
) |
|
|
|
|
|
msg_input.submit( |
|
|
fn=send_message, |
|
|
inputs=[msg_input, conversation_state], |
|
|
outputs=[chatbot, tool_display, conversation_state, msg_input] |
|
|
) |
|
|
|
|
|
clear_btn.click( |
|
|
fn=reset_conversation, |
|
|
outputs=[chatbot, tool_display, conversation_state] |
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
with gr.Tab("π§ MCP Tools"): |
|
|
gr.Markdown("### MCP Tools") |
|
|
gr.Markdown("*MCP tool integration coming soon...*") |
|
|
|
|
|
gr.Markdown(""" |
|
|
Available tools: |
|
|
- **route_optimizer** - Optimize delivery routes |
|
|
- **geocoder** - Convert addresses to coordinates |
|
|
- **weather_monitor** - Check weather conditions |
|
|
- **traffic_checker** - Monitor traffic conditions |
|
|
- **distance_matrix** - Calculate distances |
|
|
- **order_manager** - Manage orders via MCP |
|
|
""") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
with gr.Tab("πΎ Database"): |
|
|
gr.Markdown("### Database Information") |
|
|
|
|
|
db_info = gr.Markdown(f""" |
|
|
**Database:** PostgreSQL |
|
|
**Name:** fleetmind |
|
|
**Host:** localhost |
|
|
**Port:** 5432 |
|
|
|
|
|
**Tables:** |
|
|
- orders (26 columns) |
|
|
- drivers (coming soon) |
|
|
- assignments (coming soon) |
|
|
- exceptions (coming soon) |
|
|
""") |
|
|
|
|
|
test_db_btn = gr.Button("π§ͺ Test Connection", variant="primary") |
|
|
test_result = gr.Textbox(label="Test Result", interactive=False) |
|
|
|
|
|
test_db_btn.click( |
|
|
fn=lambda: "β
Connection successful!" if test_connection() else "β Connection failed", |
|
|
outputs=test_result |
|
|
) |
|
|
|
|
|
gr.Markdown("---") |
|
|
gr.Markdown("*FleetMind MCP v1.0.0 - Built with Gradio, PostgreSQL, and FastMCP*") |
|
|
|
|
|
return app |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
print("=" * 60) |
|
|
print("FleetMind MCP - Starting Gradio Server") |
|
|
print("=" * 60) |
|
|
|
|
|
|
|
|
print("\nChecking database connection...") |
|
|
if test_connection(): |
|
|
print("β
Database connected") |
|
|
else: |
|
|
print("β Database connection failed") |
|
|
print("Please check your .env file and PostgreSQL server") |
|
|
|
|
|
print("\nStarting Gradio interface...") |
|
|
print("=" * 60) |
|
|
|
|
|
|
|
|
app = create_interface() |
|
|
app.launch( |
|
|
server_name="0.0.0.0", |
|
|
server_port=7860, |
|
|
share=False, |
|
|
show_error=True |
|
|
) |
|
|
|