File size: 12,828 Bytes
0cd97e1 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 | 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()
)
|