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()
    )