| import os |
| import gradio as gr |
| import json |
| from datetime import datetime |
| from backend.conversation_engine import ConversationEngine |
| from backend.storage import Storage |
|
|
|
|
| |
| 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 |
| |
| |
| response, metadata = engine.process_message(message) |
| |
| |
| 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:]): |
| 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:]): |
| 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:]): |
| 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) |
|
|
|
|
| |
| 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(): |
| |
| 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") |
| |
| |
| 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! |
| """) |
| |
| |
| 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) |
| |
| |
| 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 |
| """) |
| |
| |
| def handle_chat_submit(message, history): |
| """Handle chat message submission and save to log.""" |
| new_history = chat_interface(message, history) |
| |
| |
| 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 |
| |
| 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__": |
| |
| 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.") |
| |
| |
| demo.launch( |
| server_name="0.0.0.0", |
| server_port=7860, |
| share=False, |
| show_error=True, |
| theme=gr.themes.Soft() |
| ) |
|
|