#!/usr/bin/env python3 """ OmniTech Customer Support Chatbot - Gradio Interface ═══════════════════════════════════════════════════════════════════════════════ OVERVIEW -------- This file creates the web-based user interface for the OmniTech Customer Support chatbot using Gradio (https://gradio.app). It provides: 1. CHAT TAB - Customer-facing chat interface where users ask questions 2. AGENT DASHBOARD TAB - Shows RAG pipeline metrics and debug info (Developer Mode) 3. MCP MONITOR TAB - Displays MCP server status and tool activity (Developer Mode) 4. KNOWLEDGE SEARCH TAB - Direct search of the knowledge base (Developer Mode) 5. TICKETS TAB - View and filter support tickets (Developer Mode) KEY CONCEPTS ------------ - Gradio Blocks: The main container for building custom UIs with full control - Gradio Tabs: Used to organize different views/screens - gr.HTML: Renders custom HTML for rich displays and styling - Event Handlers: Connect UI components (buttons, inputs) to Python functions ARCHITECTURE ------------ ┌─────────────────────────────────────────────────────────────┐ │ Gradio Web Interface │ ├─────────────────────────────────────────────────────────────┤ │ [Chat] [Agent Dashboard] [MCP Monitor] [Knowledge] [Tickets] │ │ │ │ AppState │ │ (manages agent) │ │ │ │ │ SyncAgent │ │ (from rag_agent.py) │ │ │ │ │ MCP Server Tools │ │ (from mcp_server.py) │ └─────────────────────────────────────────────────────────────┘ DEVELOPER MODE -------------- Toggle "Developer Mode" checkbox to reveal debug tabs: - Agent Dashboard: See RAG metrics, LLM prompts, and responses - MCP Monitor: View tool calls, timing, and server stats - Knowledge Search: Query the vector database directly - Tickets: View support tickets created by the system This integrates with the RAG agent (rag_agent.py) and MCP server (mcp_server.py). """ # ═══════════════════════════════════════════════════════════════════════════════ # IMPORTS # ═══════════════════════════════════════════════════════════════════════════════ import gradio as gr # Gradio library for building web UIs import json # JSON parsing for debug displays from datetime import datetime # Timestamps for chat messages from typing import Dict, List, Any # Type hints for better code clarity # ───────────────────────────────────────────────────────────────────────────── # RAG Agent Import # Try to import the RAG agent - if it fails, the UI runs in "demo mode" # This allows testing the UI without the full backend running # ───────────────────────────────────────────────────────────────────────────── try: from rag_agent import SyncAgent AGENT_AVAILABLE = True except ImportError: AGENT_AVAILABLE = False print("Warning: rag_agent not found. Running in demo mode.") # ╔══════════════════════════════════════════════════════════════════════════╗ # ║ SECTION 1: APPLICATION STATE ║ # ║ ║ # ║ Purpose: Centralized state management for the entire application ║ # ║ ║ # ║ The AppState class holds: ║ # ║ - The RAG agent instance (initialized lazily on first query) ║ # ║ - Conversation history for display in the chat ║ # ║ - Session metrics (queries, resolutions, tickets) ║ # ║ - Debug information from the last query (for Agent Dashboard) ║ # ║ ║ # ║ WHY A CLASS? Using a class instead of global variables provides: ║ # ║ 1. Encapsulation - all state in one place ║ # ║ 2. Clear initialization - __init__ sets up defaults ║ # ║ 3. Methods - related functionality grouped together ║ # ╚══════════════════════════════════════════════════════════════════════════╝ class AppState: """Global application state.""" def __init__(self): self.agent = None self.conversation_history: List[Dict] = [] self.metrics = { 'total_queries': 0, 'resolved_queries': 0, 'tickets_created': 0 } # Store last debug info for Agent Dashboard self.last_prompt = "" self.last_response = "" def initialize_agent(self) -> bool: """Initialize the MCP agent.""" if not AGENT_AVAILABLE: return False if self.agent is None: try: self.agent = SyncAgent() print("Agent initialized successfully") return True except Exception as e: print(f"Failed to initialize agent: {e}") return False return True def process_query(self, query: str, email: str) -> Dict[str, Any]: """Process a query through the agent.""" if self.agent: return self.agent.process_query(query, email) else: # Demo mode response return { "response": f"[Demo Mode] Received: {query}", "workflow": "demo", "confidence": 0.5 } def get_mcp_stats(self) -> Dict[str, Any]: """Get MCP server statistics.""" if self.agent: return self.agent.get_server_stats() return {"status": "Demo mode - no server"} def search_knowledge(self, query: str, max_results: int = 3) -> List[Dict]: """Search knowledge base directly via MCP.""" if self.agent: try: # Call the search_knowledge tool via the agent's internal method # SyncAgent.agent = OmniTechAgent, SyncAgent.loop = event loop result = self.agent.loop.run_until_complete( self.agent.agent.call_tool("search_knowledge", { "query": query, "max_results": max_results }) ) return result.get("matches", []) except Exception as e: print(f"Knowledge search error: {e}") return [] def get_tickets(self, customer_email: str = None, status: str = None) -> List[Dict]: """Get tickets via MCP tool.""" if self.agent: try: args = {"limit": 50} if customer_email: args["customer_email"] = customer_email if status: args["status"] = status result = self.agent.loop.run_until_complete( self.agent.agent.call_tool("get_tickets", args) ) return result.get("tickets", []) except Exception as e: print(f"Get tickets error: {e}") return [] def get_security_log(self) -> List[Dict]: """Get security event log from agent.""" if self.agent: return self.agent.get_security_log() return [] def clear_security_log(self): """Clear the security log.""" if self.agent: self.agent.clear_security_log() app_state = AppState() # ╔══════════════════════════════════════════════════════════════════════════╗ # ║ SECTION 2: CUSTOM CSS STYLES ║ # ║ ║ # ║ Purpose: Define custom styling for the Gradio interface ║ # ║ ║ # ║ Gradio allows injecting CSS to customize the look and feel. We inject ║ # ║ this CSS via gr.HTML() since Gradio 6.0 doesn't support the css= ║ # ║ parameter on gr.Blocks(). ║ # ║ ║ # ║ KEY CSS CLASSES: ║ # ║ .metric-card - Styled cards for displaying metrics/info ║ # ║ .chat-message-* - Styling for chat bubbles (user vs agent) ║ # ║ .tool-card - Styling for MCP tool displays ║ # ║ .nav-button - Navigation button styling ║ # ╚══════════════════════════════════════════════════════════════════════════╝ CUSTOM_CSS = """ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap'); .gradio-container { font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif !important; } /* Debug toggle styling */ .debug-toggle { margin-top: 1rem; padding: 0.5rem 1rem; background: #f1f5f9; border-radius: 8px; font-size: 0.875rem; } .debug-toggle label { cursor: pointer; } /* Typing indicator animation */ @keyframes typing-dot { 0%, 60%, 100% { opacity: 0.3; } 30% { opacity: 1; } } .typing-indicator { display: inline-flex; gap: 4px; padding: 0.75rem 1rem; background: #f1f5f9; border-radius: 12px; margin: 0.5rem 0; } .typing-indicator span { width: 8px; height: 8px; background: #64748b; border-radius: 50%; animation: typing-dot 1.4s infinite; } .typing-indicator span:nth-child(2) { animation-delay: 0.2s; } .typing-indicator span:nth-child(3) { animation-delay: 0.4s; } .nav-button { background: #ffffff; border: 1.5px solid #e2e8f0 !important; border-radius: 10px; padding: 0.875rem 1.25rem; margin: 0.375rem 0; transition: all 0.2s ease; font-weight: 500; color: #475569; } .nav-button:hover { background: #f8fafc; border-color: #cbd5e1 !important; transform: translateX(4px); } .metric-card { background: #ffffff; border: 1px solid #e2e8f0; border-radius: 10px; padding: 1.25rem; margin: 0.5rem 0; transition: all 0.2s ease; } .metric-card:hover { border-color: #cbd5e1; box-shadow: 0 4px 8px -2px rgba(0, 0, 0, 0.08); } .chat-message-user { background: #ffffff; border: 1px solid #e2e8f0; border-left: 3px solid #3b82f6; border-radius: 10px; padding: 1rem; margin: 0.5rem 0; } .chat-message-agent { background: #f8fafc; border: 1px solid #e2e8f0; border-left: 3px solid #10b981; border-radius: 10px; padding: 1rem; margin: 0.5rem 0; } .tool-card { background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 8px; padding: 0.75rem 1rem; margin: 0.375rem 0; font-family: 'SF Mono', Monaco, monospace; font-size: 0.875rem; } """ # ╔══════════════════════════════════════════════════════════════════════════╗ # ║ SECTION 3: HELPER FUNCTIONS ║ # ║ ║ # ║ Purpose: Utility functions for formatting and generating HTML content ║ # ║ ║ # ║ FUNCTIONS IN THIS SECTION: ║ # ║ format_message() - Format chat messages as styled HTML ║ # ║ process_query_handler() - Main handler for user query submission ║ # ║ generate_agent_dashboard() - Generate Agent Dashboard HTML ║ # ║ generate_mcp_monitor() - Generate MCP Monitor HTML ║ # ║ generate_tickets_display() - Generate Tickets tab HTML ║ # ║ clear_chat() - Reset conversation and metrics ║ # ║ get_status() - Get system status string ║ # ║ search_knowledge_direct() - Search knowledge base directly ║ # ╚══════════════════════════════════════════════════════════════════════════╝ def format_message(sender: str, content: str, timestamp: str) -> str: """Format a chat message as HTML.""" msg_class = "chat-message-agent" if sender == "agent" else "chat-message-user" sender_name = "AI Agent" if sender == "agent" else "You" return f"""
{sender_name} {timestamp}
{content}
""" def process_query_handler(query: str, customer_email: str, history: str): """Process a query and update chat history.""" if not query.strip(): return history, "", "", "" timestamp = datetime.now().strftime("%H:%M:%S") history += format_message("customer", query, timestamp) # Initialize agent if needed if not app_state.agent: app_state.initialize_agent() # Process query result = app_state.process_query(query, customer_email) response = result.get("response", "Error processing query") # Add to history app_state.conversation_history.append({ 'sender': 'customer', 'content': query, 'timestamp': timestamp }) app_state.conversation_history.append({ 'sender': 'agent', 'content': response, 'timestamp': datetime.now().strftime("%H:%M:%S"), 'metadata': result }) # Update metrics app_state.metrics['total_queries'] += 1 if result.get('confidence', 0) > 0.7: app_state.metrics['resolved_queries'] += 1 if result.get('ticket_created'): app_state.metrics['tickets_created'] += 1 history += format_message("agent", response, datetime.now().strftime("%H:%M:%S")) # Get prompt and response for debug display prompt = result.get('llm_prompt', 'No prompt available') response_json = json.dumps({k: v for k, v in result.items() if k != 'llm_prompt'}, indent=2) # Store in app_state for Agent Dashboard app_state.last_prompt = prompt app_state.last_response = response_json return history, "", prompt, response_json def generate_agent_dashboard() -> str: """Generate the agent dashboard HTML.""" metrics = app_state.metrics total = metrics['total_queries'] resolved = metrics['resolved_queries'] tickets = metrics['tickets_created'] rate = f"{(resolved/max(total,1)*100):.1f}%" html = """

Agent Performance Dashboard

Real-time analytics and RAG pipeline metrics

""" # Metrics row html += f"""

TOTAL QUERIES

{total}

RESOLUTION RATE

{rate}

TICKETS CREATED

{tickets}

""" # Recent queries with RAG details agent_msgs = [m for m in app_state.conversation_history if m['sender'] == 'agent'] if agent_msgs: html += "

Recent RAG Operations

" for i, msg in enumerate(agent_msgs[-5:], 1): meta = msg.get('metadata', {}) workflow = meta.get('workflow', 'unknown') category = meta.get('classification', {}).get('category', 'N/A') sources = meta.get('sources', []) confidence = meta.get('confidence', 0) html += f"""

Query {i} - {msg.get('timestamp', 'N/A')}

Workflow: {workflow}

Category: {category}

Confidence: {confidence:.2%}

Sources: {', '.join(sources) if sources else 'None'}

""" else: html += "

No queries processed yet. Start chatting to see RAG analytics.

" return html def generate_mcp_monitor() -> str: """Generate the MCP monitor HTML.""" stats = app_state.get_mcp_stats() html = """

MCP Protocol Monitor

Server status and tool activity

""" if isinstance(stats, dict) and 'error' not in stats: # Server metrics html += f"""

TOTAL REQUESTS

{stats.get('total_requests', 0)}

KNOWLEDGE DOCS

{stats.get('knowledge_documents', 0)}

CUSTOMERS

{stats.get('customers_in_db', 0)}

""" # Recent MCP calls (shown first) if app_state.agent: mcp_log = app_state.agent.get_mcp_log() if mcp_log: html += "

Recent MCP Calls

" for entry in mcp_log[-10:]: html += f"""
{entry['tool']} {entry['duration_ms']}ms

{entry['timestamp']} - {'✓' if entry['success'] else '✗'}

""" # Available tools tools = stats.get('tools_available', []) if tools: html += "

Available MCP Tools

" tool_descriptions = { 'classify_query': 'Classify customer queries into support categories', 'get_query_template': 'Get prompt templates for categories', 'list_categories': 'List all support categories', 'search_knowledge': 'Search the knowledge base', 'get_knowledge_for_query': 'Get knowledge for a category', 'lookup_customer': 'Look up customer information', 'create_support_ticket': 'Create support tickets', 'get_server_stats': 'Get server statistics' } for tool in tools: desc = tool_descriptions.get(tool, 'MCP tool') html += f"""
{tool}

{desc}

""" html += "
" else: html += "

Server statistics not available. Initialize the agent first.

" return html def generate_tickets_display(customer_filter: str = "", status_filter: str = "") -> str: """Generate HTML display for tickets.""" # Ensure agent is initialized if not app_state.agent: app_state.initialize_agent() # Apply filters customer = customer_filter if customer_filter and customer_filter != "All" else None status = status_filter if status_filter and status_filter != "All" else None tickets = app_state.get_tickets(customer_email=customer, status=status) html = """

Support Tickets

View and track customer support tickets

""" if not app_state.agent: html += """

Agent not initialized

Send a chat message first to initialize the system.

""" return html if not tickets: html += """

No tickets found

Tickets will appear here when customers submit support requests.

""" return html # Summary stats open_count = len([t for t in tickets if t.get("status") == "Open"]) closed_count = len(tickets) - open_count html += f"""

TOTAL TICKETS

{len(tickets)}

OPEN

{open_count}

CLOSED

{closed_count}

""" # Tickets list html += "
" for ticket in tickets: status = ticket.get("status", "Unknown") priority = ticket.get("priority", "medium") # Status badge color status_color = "#f59e0b" if status == "Open" else "#10b981" status_bg = "#fef3c7" if status == "Open" else "#d1fae5" # Priority badge priority_colors = { "high": ("#dc2626", "#fee2e2"), "medium": ("#f59e0b", "#fef3c7"), "low": ("#3b82f6", "#dbeafe") } pri_color, pri_bg = priority_colors.get(priority, ("#64748b", "#f1f5f9")) html += f"""
{ticket.get('id', 'N/A')} {status} {priority}
{ticket.get('created_at', 'N/A')[:16]}

Customer: {ticket.get('customer_email', 'N/A')}

Issue: {ticket.get('issue_type', 'N/A')}

{ticket.get('description', 'No description')[:200]}{'...' if len(ticket.get('description', '')) > 200 else ''}

""" html += "
" return html def generate_security_log_display() -> str: """Generate HTML display for security events log.""" events = app_state.get_security_log() html = """

Security Monitor

Track potential prompt injection and goal-hijacking attempts

""" if not app_state.agent: html += """

Agent not initialized

Send a chat message first to initialize the system.

""" return html # Summary stats high_count = len([e for e in events if e.get("severity") == "high"]) medium_count = len([e for e in events if e.get("severity") == "medium"]) low_count = len([e for e in events if e.get("severity") == "low"]) html += f"""

TOTAL EVENTS

{len(events)}

HIGH SEVERITY

{high_count}

MEDIUM SEVERITY

{medium_count}

LOW SEVERITY

{low_count}

""" # Detection patterns info html += """

Monitored Patterns

The agent monitors for common prompt injection patterns including: ignore instructions, role changes, fake system prompts, reveal prompt attempts, and more.

""" if not events: html += """

✓ No suspicious activity detected

All queries have passed security inspection.

""" return html # Events list (most recent first) html += "

Security Events

" html += "
" for event in reversed(events[-20:]): # Show last 20, most recent first severity = event.get("severity", "low") event_type = event.get("event_type", "unknown") details = event.get("details", "No details") query = event.get("query", "") timestamp = event.get("timestamp", "")[:19] # Trim microseconds customer = event.get("customer_email", "N/A") # Severity badge colors severity_colors = { "high": ("#dc2626", "#fee2e2"), "medium": ("#f59e0b", "#fef3c7"), "low": ("#3b82f6", "#dbeafe") } sev_color, sev_bg = severity_colors.get(severity, ("#64748b", "#f1f5f9")) html += f"""
{event_type} {severity}
{timestamp}

Details: {details}

Customer: {customer}

{f'
{query[:150]}{"..." if len(query) > 150 else ""}
' if query else ''}
""" html += "
" return html def clear_chat(): """Clear conversation history.""" app_state.conversation_history = [] app_state.metrics = {'total_queries': 0, 'resolved_queries': 0, 'tickets_created': 0} initial_html = """

How can we help you today?

Ask about orders, products, accounts, or technical support.

""" return initial_html, "", "" def get_status() -> str: """Get system status.""" if app_state.agent: tools = app_state.agent.get_available_tools() return f"**System Online**\n\nMCP Tools: {len(tools)}\n\nReady to assist" else: return "**Initializing...**\n\nClick 'Send' to start" def search_knowledge_direct(search_query: str, max_results: int) -> str: """Direct knowledge base search and format results as HTML.""" if not search_query.strip(): return """

Enter a search query to search the knowledge base.

""" results = app_state.search_knowledge(search_query, int(max_results)) if not results: return """

No results found. Try different search terms.

""" html = f"""

Search Results for: "{search_query}"

Found {len(results)} documents

""" for i, doc in enumerate(results, 1): similarity = doc.get('similarity', 0) category = doc.get('category', 'unknown') content = doc.get('content', '')[:500] source = doc.get('source', 'Unknown') # Visual similarity bar bar_filled = int(similarity * 10) if similarity > 0 else 0 similarity_bar = "█" * bar_filled + "░" * (10 - bar_filled) html += f"""

Result {i}: {category.replace('_', ' ').title()}

Source: {source}

Similarity: {similarity:.3f} {similarity_bar}

{content}...

""" return html # ╔══════════════════════════════════════════════════════════════════════════╗ # ║ SECTION 4: GRADIO INTERFACE DEFINITION ║ # ║ ║ # ║ Purpose: Define the complete Gradio UI layout and event handlers ║ # ║ ║ # ║ STRUCTURE: ║ # ║ 1. gr.Blocks() - Main container with title ║ # ║ 2. Header Row - Title banner + Developer Mode toggle ║ # ║ 3. gr.Tabs() containing all tabs: ║ # ║ - Chat Tab (always visible) - Main customer interface ║ # ║ - Agent Dashboard Tab (Developer Mode) - RAG metrics & debug ║ # ║ - MCP Monitor Tab (Developer Mode) - Server stats & tools ║ # ║ - Knowledge Search Tab (Developer Mode) - Direct KB search ║ # ║ - Tickets Tab (Developer Mode) - View support tickets ║ # ║ 4. Footer - Branding and copyright ║ # ║ 5. Event Handlers - Connect UI components to Python functions ║ # ║ ║ # ║ KEY GRADIO CONCEPTS: ║ # ║ - gr.Blocks: Container for custom layouts (vs gr.Interface) ║ # ║ - gr.Row/Column: Layout containers for organizing components ║ # ║ - gr.Tab: Tab panels within gr.Tabs ║ # ║ - gr.HTML: Render custom HTML content ║ # ║ - gr.Button: Clickable buttons with variants (primary/secondary) ║ # ║ - gr.Textbox: Text input fields ║ # ║ - gr.Dropdown: Selection dropdowns ║ # ║ - gr.Checkbox: Toggle switches ║ # ║ - .click()/.change()/.submit(): Event handler decorators ║ # ║ - gr.update(): Return value to update component properties ║ # ╚══════════════════════════════════════════════════════════════════════════╝ with gr.Blocks(title="OmniTech Support") as demo: # Inject custom CSS gr.HTML(f"") # Header with debug toggle with gr.Row(): with gr.Column(scale=20): gr.HTML("""

OmniTech Customer Support

AI-Powered Support Assistant

""") with gr.Column(scale=1, min_width=180): debug_mode = gr.Checkbox( label="Developer Mode", value=False ) # Status display (only visible in debug mode) status_display = gr.Markdown(get_status(), visible=False) # Use Tabs for navigation (Gradio 6.0 compatible) with gr.Tabs() as tabs: # Customer Chat Tab with gr.Tab("Chat", id="chat_tab"): with gr.Row(): with gr.Column(scale=4): customer_email = gr.Dropdown( label="Customer", choices=["john.doe@email.com", "sarah.smith@email.com", "mike.johnson@email.com", "guest@example.com"], value="john.doe@email.com", container=True ) with gr.Column(scale=1): clear_btn = gr.Button("Clear Chat", size="sm") chat_display = gr.HTML( value="""

How can we help you today?

Ask about orders, products, accounts, or technical support.

""" ) query_input = gr.Textbox( label="Message", placeholder="Type your question here...", lines=2, show_label=False ) with gr.Row(): send_btn = gr.Button("Send Message", variant="primary", scale=3) gr.Markdown("**Try asking about:**", elem_classes=["quick-actions-label"]) with gr.Row(): q1 = gr.Button("🔑 Password Reset", size="sm", variant="secondary") q2 = gr.Button("🔧 Device Issue", size="sm", variant="secondary") q3 = gr.Button("📦 Return Policy", size="sm", variant="secondary") q4 = gr.Button("🚚 Track Order", size="sm", variant="secondary") # Agent Dashboard Tab (hidden by default) with gr.Tab("Agent Dashboard", id="agent_tab", visible=False) as agent_tab: agent_dashboard = gr.HTML(generate_agent_dashboard()) refresh_agent_btn = gr.Button("Refresh Dashboard") # Debug Info Section - LLM Prompt and Full Response gr.Markdown("---") gr.Markdown("### Last Query Debug Info") with gr.Row(): with gr.Column(): prompt_display = gr.Textbox(label="LLM Prompt", lines=10, interactive=False) with gr.Column(): response_display = gr.Textbox(label="Full Response", lines=10, interactive=False) # MCP Monitor Tab (hidden by default) with gr.Tab("MCP Monitor", id="mcp_tab", visible=False) as mcp_tab: mcp_monitor = gr.HTML(generate_mcp_monitor()) refresh_mcp_btn = gr.Button("Refresh Monitor") # Knowledge Search Tab (hidden by default) with gr.Tab("Knowledge Search", visible=False) as kb_tab: gr.Markdown("## Knowledge Base Search") gr.Markdown("Search the OmniTech product documentation directly.") with gr.Row(): with gr.Column(scale=3): search_input = gr.Textbox( label="Search Query", placeholder="Enter search terms (e.g., 'password reset', 'warranty', 'smart home')", lines=1 ) with gr.Column(scale=1): search_results_slider = gr.Slider( label="Max Results", minimum=1, maximum=10, value=3, step=1 ) search_btn = gr.Button("Search Knowledge Base", variant="primary") knowledge_results = gr.HTML( value="""

Enter a search query to explore the OmniTech knowledge base.

Try queries like: "password reset", "device troubleshooting", "return policy", "warranty information"

""" ) gr.Markdown("---") gr.Markdown("### Knowledge Base Categories") gr.HTML("""

Account Security

Password reset, 2FA, account recovery

Device Support

Troubleshooting, setup, compatibility

Shipping & Returns

Order tracking, return policy, refunds

""") # Tickets Tab (hidden by default) with gr.Tab("Tickets", id="tickets_tab", visible=False) as tickets_tab: with gr.Row(): with gr.Column(scale=2): ticket_customer_filter = gr.Dropdown( label="Filter by Customer", choices=["All", "john.doe@email.com", "sarah.smith@email.com", "mike.johnson@email.com"], value="All" ) with gr.Column(scale=2): ticket_status_filter = gr.Dropdown( label="Filter by Status", choices=["All", "Open", "Closed"], value="All" ) with gr.Column(scale=1): refresh_tickets_btn = gr.Button("Refresh", variant="secondary") tickets_display = gr.HTML(generate_tickets_display()) # Security Log Tab (hidden by default) with gr.Tab("Security", id="security_tab", visible=False) as security_tab: security_display = gr.HTML(generate_security_log_display()) with gr.Row(): refresh_security_btn = gr.Button("Refresh", variant="secondary") clear_security_btn = gr.Button("Clear Log", variant="secondary") # Footer gr.HTML("""
OmniTech Customer Support • Enterprise AI Accelerator Capstone
© 2026 Tech Skills Transformations
""") # Debug mode toggle handler def toggle_debug_mode(enabled): """Toggle visibility of debug elements.""" return ( gr.update(visible=enabled), # agent_tab gr.update(visible=enabled), # mcp_tab gr.update(visible=enabled), # kb_tab gr.update(visible=enabled), # tickets_tab gr.update(visible=enabled), # security_tab gr.update(visible=enabled), # status_display gr.update(selected="chat_tab"), # tabs - always select chat tab on toggle ) debug_mode.change( toggle_debug_mode, inputs=[debug_mode], outputs=[agent_tab, mcp_tab, kb_tab, tickets_tab, security_tab, status_display, tabs] ) # Chat handlers send_btn.click( process_query_handler, inputs=[query_input, customer_email, chat_display], outputs=[chat_display, query_input, prompt_display, response_display] ) query_input.submit( process_query_handler, inputs=[query_input, customer_email, chat_display], outputs=[chat_display, query_input, prompt_display, response_display] ) clear_btn.click(clear_chat, outputs=[chat_display, prompt_display, response_display]) # Quick action handlers q1.click(lambda: "How do I reset my password?", outputs=query_input) q2.click(lambda: "My device won't turn on", outputs=query_input) q3.click(lambda: "What is your return policy?", outputs=query_input) q4.click(lambda: "How can I track my order?", outputs=query_input) # Refresh handlers refresh_agent_btn.click(lambda: generate_agent_dashboard(), outputs=agent_dashboard) refresh_mcp_btn.click(lambda: generate_mcp_monitor(), outputs=mcp_monitor) # Auto-refresh dashboards when switching tabs def refresh_agent_tab(): """Refresh agent dashboard and debug info.""" return generate_agent_dashboard(), app_state.last_prompt, app_state.last_response agent_tab.select(refresh_agent_tab, outputs=[agent_dashboard, prompt_display, response_display]) mcp_tab.select(lambda: generate_mcp_monitor(), outputs=mcp_monitor) # Knowledge search handler search_btn.click( search_knowledge_direct, inputs=[search_input, search_results_slider], outputs=knowledge_results ) search_input.submit( search_knowledge_direct, inputs=[search_input, search_results_slider], outputs=knowledge_results ) # Tickets handlers def refresh_tickets(customer, status): return generate_tickets_display(customer, status) refresh_tickets_btn.click( refresh_tickets, inputs=[ticket_customer_filter, ticket_status_filter], outputs=tickets_display ) ticket_customer_filter.change( refresh_tickets, inputs=[ticket_customer_filter, ticket_status_filter], outputs=tickets_display ) ticket_status_filter.change( refresh_tickets, inputs=[ticket_customer_filter, ticket_status_filter], outputs=tickets_display ) def refresh_tickets_on_select(): """Refresh tickets and status when switching to tickets tab.""" return generate_tickets_display(), get_status() tickets_tab.select( refresh_tickets_on_select, outputs=[tickets_display, status_display] ) # Security log handlers def refresh_security(): return generate_security_log_display() def clear_security(): app_state.clear_security_log() return generate_security_log_display() refresh_security_btn.click(refresh_security, outputs=security_display) clear_security_btn.click(clear_security, outputs=security_display) security_tab.select(refresh_security, outputs=security_display) # Initialize on load demo.load(lambda: (app_state.initialize_agent(), get_status())[1], outputs=status_display) # ╔══════════════════════════════════════════════════════════════════════════╗ # ║ 5. Main Entry Point ║ # ╚══════════════════════════════════════════════════════════════════════════╝ if __name__ == "__main__": print("=" * 60) print("OmniTech Customer Support Chatbot") print("=" * 60) print(f"Agent available: {AGENT_AVAILABLE}") print("Starting Gradio interface...") print("=" * 60) demo.launch( server_name="0.0.0.0", server_port=7860, share=True, footer_links=[ {"text": "© 2026 Tech Skills Transformations", "url": "https://getskillsnow.com"} ] )