Hamza4100's picture
Update app.py
0cd97e1 verified
import os
import gradio as gr
import json
from datetime import datetime
from backend.conversation_engine import ConversationEngine
from backend.storage import Storage
# Global conversation engine instance
engine = ConversationEngine()
storage = Storage("data")
def chat_interface(message, history):
"""
Main chat interface handler compatible with Gradio 6.x.
Args:
message: User's input message
history: Chat history list of message dicts
Returns:
Updated chat history with new bot response
"""
if not message.strip():
return history
# Process message through engine
response, metadata = engine.process_message(message)
# Return in Gradio 6.x format: list of {"role": "user"/"assistant", "content": "..."}
return history + [{"role": "user", "content": message}, {"role": "assistant", "content": response}]
def new_conversation():
"""Reset conversation for new session."""
engine.reset()
return [], "βœ… Conversation reset. Start fresh!"
def get_admin_leads_view():
"""Get HTML view of all leads."""
leads = storage.get_all_leads()
if not leads:
return "<p>No leads yet.</p>"
html = "<table style='width:100%; border-collapse: collapse;'>"
html += "<tr style='background: #f0f0f0;'><th style='border: 1px solid #ddd; padding: 8px;'>ID</th><th style='border: 1px solid #ddd; padding: 8px;'>Name</th><th style='border: 1px solid #ddd; padding: 8px;'>Business</th><th style='border: 1px solid #ddd; padding: 8px;'>Budget</th><th style='border: 1px solid #ddd; padding: 8px;'>Email</th><th style='border: 1px solid #ddd; padding: 8px;'>Score</th><th style='border: 1px solid #ddd; padding: 8px;'>Created</th></tr>"
for lead in reversed(leads[-20:]): # Show last 20
name = lead.get("user_name", "N/A")
business = lead.get("business_type", "N/A")[:30]
budget = lead.get("budget", "N/A")[:20]
email = lead.get("email", "N/A")
score_info = lead.get("scoring", {})
score = f"{score_info.get('score', 0)} ({score_info.get('classification', 'Unknown')})"
created = lead.get("created_at", "N/A").split("T")[0]
html += f"<tr><td style='border: 1px solid #ddd; padding: 8px;'>{lead['id'][:10]}...</td><td style='border: 1px solid #ddd; padding: 8px;'>{name}</td><td style='border: 1px solid #ddd; padding: 8px;'>{business}</td><td style='border: 1px solid #ddd; padding: 8px;'>{budget}</td><td style='border: 1px solid #ddd; padding: 8px;'>{email}</td><td style='border: 1px solid #ddd; padding: 8px;'>{score}</td><td style='border: 1px solid #ddd; padding: 8px;'>{created}</td></tr>"
html += "</table>"
return html
def get_admin_tickets_view():
"""Get HTML view of all support tickets."""
tickets = storage.get_all_tickets()
if not tickets:
return "<p>No support tickets yet.</p>"
html = "<table style='width:100%; border-collapse: collapse;'>"
html += "<tr style='background: #f0f0f0;'><th style='border: 1px solid #ddd; padding: 8px;'>ID</th><th style='border: 1px solid #ddd; padding: 8px;'>Name</th><th style='border: 1px solid #ddd; padding: 8px;'>Issue</th><th style='border: 1px solid #ddd; padding: 8px;'>Reference</th><th style='border: 1px solid #ddd; padding: 8px;'>Status</th><th style='border: 1px solid #ddd; padding: 8px;'>Created</th></tr>"
for ticket in reversed(tickets[-20:]): # Show last 20
name = ticket.get("user_name", "N/A")
issue = ticket.get("issue_description", "N/A")[:50]
ref_id = ticket.get("reference_id", "N/A")[:20]
status = ticket.get("status", "N/A")
created = ticket.get("created_at", "N/A").split("T")[0]
html += f"<tr><td style='border: 1px solid #ddd; padding: 8px;'>{ticket['id'][:10]}...</td><td style='border: 1px solid #ddd; padding: 8px;'>{name}</td><td style='border: 1px solid #ddd; padding: 8px;'>{issue}</td><td style='border: 1px solid #ddd; padding: 8px;'>{ref_id}</td><td style='border: 1px solid #ddd; padding: 8px;'><span style='background: #fff3cd; padding: 2px 6px; border-radius: 3px;'>{status}</span></td><td style='border: 1px solid #ddd; padding: 8px;'>{created}</td></tr>"
html += "</table>"
return html
def get_admin_logs_view():
"""Get HTML view of conversation logs summary."""
convs = storage.get_all_conversations()
summary = storage.get_summary()
html = f"""
<div style='padding: 16px; background: #f9f9f9; border-radius: 8px;'>
<h3>System Summary</h3>
<p><strong>Total Conversations:</strong> {summary['total_conversations']}</p>
<p><strong>Total Leads:</strong> {summary['total_leads']}</p>
<p><strong>Total Support Tickets:</strong> {summary['total_tickets']}</p>
</div>
<h3>Recent Conversations</h3>
"""
if not convs:
html += "<p>No conversations logged yet.</p>"
else:
html += "<table style='width:100%; border-collapse: collapse;'>"
html += "<tr style='background: #f0f0f0;'><th style='border: 1px solid #ddd; padding: 8px;'>ID</th><th style='border: 1px solid #ddd; padding: 8px;'>Intent</th><th style='border: 1px solid #ddd; padding: 8px;'>Messages</th><th style='border: 1px solid #ddd; padding: 8px;'>Timestamp</th></tr>"
for conv in reversed(convs[-20:]): # Show last 20
intent = conv.get("intent", "unknown")
msg_count = len(conv.get("messages", []))
timestamp = conv.get("timestamp", "N/A").split("T")[0]
html += f"<tr><td style='border: 1px solid #ddd; padding: 8px;'>{conv['id'][:10]}...</td><td style='border: 1px solid #ddd; padding: 8px;'>{intent}</td><td style='border: 1px solid #ddd; padding: 8px;'>{msg_count}</td><td style='border: 1px solid #ddd; padding: 8px;'>{timestamp}</td></tr>"
html += "</table>"
return html
def export_leads_json():
"""Export leads as JSON."""
leads = storage.get_all_leads()
return json.dumps(leads, indent=2)
def export_tickets_json():
"""Export tickets as JSON."""
tickets = storage.get_all_tickets()
return json.dumps(tickets, indent=2)
# Create Gradio interface (theme parameter moved to launch() in Gradio 6.x)
with gr.Blocks(title="AI Support Chatbot") as demo:
gr.Markdown("# πŸ€– AI Customer Support & Lead Qualification Chatbot")
gr.Markdown("Ask questions, qualify for services, or report issues. The AI will route your request appropriately.")
with gr.Tabs():
# Tab 1: Chat Interface
with gr.Tab("πŸ’¬ Chat"):
with gr.Column():
chatbot = gr.Chatbot(label="Conversation", height=500)
with gr.Row():
msg = gr.Textbox(
label="Your Message",
placeholder="Type your message here...",
lines=2
)
submit = gr.Button("Send", size="lg", variant="primary")
with gr.Row():
new_chat_btn = gr.Button("πŸ”„ New Conversation", size="sm")
clear_history = gr.Button("πŸ—‘οΈ Clear Chat", size="sm")
# Info box
gr.Markdown("""
### How to use:
- **Lead Inquiry**: Ask about pricing, services, or your business problem
- **Support Issue**: Describe any technical problems or issues
- **General Questions**: Ask about our company and services
The AI will detect your intent and respond accordingly!
""")
# Tab 2: Admin Dashboard
with gr.Tab("πŸ“Š Admin Panel"):
gr.Markdown("## Admin Dashboard")
with gr.Tabs():
with gr.Tab("πŸ‘₯ Leads"):
leads_html = gr.HTML()
refresh_leads = gr.Button("πŸ”„ Refresh Leads")
refresh_leads.click(get_admin_leads_view, outputs=leads_html)
leads_html.load(get_admin_leads_view, outputs=leads_html)
with gr.Tab("🎟️ Support Tickets"):
tickets_html = gr.HTML()
refresh_tickets = gr.Button("πŸ”„ Refresh Tickets")
refresh_tickets.click(get_admin_tickets_view, outputs=tickets_html)
tickets_html.load(get_admin_tickets_view, outputs=tickets_html)
with gr.Tab("πŸ“‹ Logs & Summary"):
logs_html = gr.HTML()
refresh_logs = gr.Button("πŸ”„ Refresh Logs")
refresh_logs.click(get_admin_logs_view, outputs=logs_html)
logs_html.load(get_admin_logs_view, outputs=logs_html)
with gr.Tab("πŸ“₯ Export"):
gr.Markdown("### Export Data")
with gr.Row():
with gr.Column():
gr.Markdown("**Leads Export**")
export_leads_btn = gr.Button("Export Leads as JSON")
leads_json = gr.Textbox(label="Leads JSON", lines=10, interactive=False)
export_leads_btn.click(export_leads_json, outputs=leads_json)
with gr.Column():
gr.Markdown("**Support Tickets Export**")
export_tickets_btn = gr.Button("Export Tickets as JSON")
tickets_json = gr.Textbox(label="Tickets JSON", lines=10, interactive=False)
export_tickets_btn.click(export_tickets_json, outputs=tickets_json)
# Tab 3: About
with gr.Tab("ℹ️ About"):
gr.Markdown("""
# About This Chatbot
## Features
- **Intent Detection**: Automatically classifies user messages (lead, support, general)
- **Multi-Flow Routing**: Routes conversations based on detected intent
- **Lead Qualification**: Captures lead information with automatic scoring
- **Support Ticketing**: Creates support tickets with conversation context
- **Smart Memory**: Maintains conversation context (last 10 messages)
- **FAQ Retrieval**: Answers common questions from knowledge base
- **Hallucination Control**: Grounds responses and avoids making up information
- **Lead Scoring**: Rates leads as Hot/Warm/Cold based on engagement signals
## Technology Stack
- **Frontend**: Gradio (Python)
- **Backend**: Python with modular architecture
- **AI**: Google Gemini 2.5 Flash API
- **Storage**: JSON files
## Designed for Hugging Face Spaces
This application is built to run directly on Hugging Face Spaces without additional infrastructure.
---
Built with ❀️ for customer support & lead qualification
""")
# Event handlers
def handle_chat_submit(message, history):
"""Handle chat message submission and save to log."""
new_history = chat_interface(message, history)
# Save to conversation log
if message.strip():
try:
storage.save_conversation({
"intent": engine.current_intent,
"user_name": engine.memory.user_name,
"message_count": len(new_history),
"timestamp": datetime.now().isoformat()
})
except:
pass # Silently fail if logging fails
return new_history, ""
submit.click(handle_chat_submit, [msg, chatbot], [chatbot, msg])
msg.submit(handle_chat_submit, [msg, chatbot], [chatbot, msg])
new_chat_btn.click(new_conversation, outputs=[chatbot, gr.State()])
clear_history.click(lambda: [], outputs=chatbot)
if __name__ == "__main__":
# Check for API key
if not os.getenv("GEMINI_API_KEY"):
print("⚠️ WARNING: GEMINI_API_KEY not set. The chatbot will use pattern-based fallback.")
print("Set the GEMINI_API_KEY environment variable to enable AI-powered responses.")
# Launch the app (theme parameter for Gradio 6.x)
demo.launch(
server_name="0.0.0.0",
server_port=7860,
share=False,
show_error=True,
theme=gr.themes.Soft()
)