import gradio as gr import os from typing import List, Tuple, Optional from agent import build_graph, run_agent REQUIRED_SECRETS = ("GROQ_API_KEY", "TAVILY_API_KEY") def missing_secrets() -> List[str]: return [name for name in REQUIRED_SECRETS if not os.getenv(name, "").strip()] class QnAChatbot: """A Q&A chatbot interface for the agent.""" def __init__(self): print("๐ค QnAChatbot initializing...") self.graph = None self.conversation_history = [] missing = missing_secrets() if missing: print(f"โ ๏ธ Space can start, but these secrets are missing: {', '.join(missing)}") print(" Hugging Face Space: Settings โ Variables and secrets") else: print("๐ง Building agent graph...") self.graph = build_graph() print("โ QnAChatbot initialized successfully") def _ensure_graph(self): if self.graph is not None: return missing = missing_secrets() if missing: raise RuntimeError( "Missing API secrets: " + ", ".join(missing) + ". Add them in Hugging Face Space Settings โ Variables and secrets, " "then restart the Space. Locally, put them in `.env`." ) print("๐ง Building agent graph on first use...") self.graph = build_graph() def process_question(self, question: str, history: List[Tuple[str, str]], uploaded_files: Optional[List] = None) -> Tuple[str, List[Tuple[str, str]]]: """Process a question and return the response with updated history.""" if not question.strip() and not uploaded_files: print("โ ๏ธ No question or files provided") return "", history try: print(f"\n{'='*60}") print(f"๐ค Processing new question...") print(f"๐ Question: {question[:100]}{'...' if len(question) > 100 else ''}") print(f"๐ Files uploaded: {len(uploaded_files) if uploaded_files else 0}") self._ensure_graph() file_paths = [] if uploaded_files: print(f"๐ Processing {len(uploaded_files)} uploaded file(s)...") file_paths = self._collect_file_paths(uploaded_files) print("๐ Invoking agent graph...") result = run_agent(question, file_paths=file_paths, graph=self.graph) print(f"๐จ Received {len(result['messages'])} message(s) from agent") print(f"๐ Extracted FINAL ANSWER: {result['final']}") answer = result["raw"] if result["final"] and result["final"] not in answer: answer = f"{answer}\n\nExtracted: {result['final']}" # Update conversation history history.append((question, answer)) print(f"โ Question processed successfully") print(f"๐ Response length: {len(answer)} characters") print(f"๐ฌ Total conversation history: {len(history)} exchanges") print(f"{'='*60}\n") return "", history except Exception as e: error_msg = f"Error processing question: {str(e)}" print(f"โ {error_msg}") print(f"๐ Exception details: {type(e).__name__}: {str(e)}") import traceback print(f"๐ Traceback:\n{traceback.format_exc()}") history.append((question, error_msg)) print(f"{'='*60}\n") return "", history def _collect_file_paths(self, uploaded_files: List) -> List[str]: """Keep local paths only; do not inline base64 into the prompt.""" paths = [] for file_path in uploaded_files: if not file_path or not os.path.exists(file_path): print(f"โ ๏ธ Skipping invalid file path: {file_path}") continue print(f"๐ File ready: {os.path.basename(file_path)} ({os.path.getsize(file_path)} bytes)") paths.append(file_path) return paths def clear_history(self): """Clear the conversation history.""" print("๐งน Clearing conversation history...") self.conversation_history = [] print("โ Conversation history cleared") return [] def create_qna_interface(): """Create the Q&A chatbot interface.""" print("๐ Creating Q&A interface...") # Initialize the chatbot chatbot = QnAChatbot() print("๐จ Setting up UI components...") # Enhanced Custom CSS for modern, professional styling custom_css = """ /* Import Google Fonts */ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap'); /* CSS Variables for Theme Support */ :root { --primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%); --secondary-gradient: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); --success-gradient: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); --warning-gradient: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%); --glass-bg: rgba(255, 255, 255, 0.95); --glass-border: rgba(255, 255, 255, 0.2); --text-primary: #2d3748; --text-secondary: #4a5568; --text-light: #718096; --bg-light: #f7fafc; --bg-card: #ffffff; --shadow-sm: 0 2px 10px rgba(0, 0, 0, 0.05); --shadow-md: 0 10px 30px rgba(0, 0, 0, 0.1); --shadow-lg: 0 20px 40px rgba(0, 0, 0, 0.15); --border-radius: 12px; --border-radius-lg: 20px; --transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); } /* Global Styles */ * { font-family: 'Inter', sans-serif !important; box-sizing: border-box !important; } /* Main Container with Enhanced Background */ .gradio-container { max-width: 1400px !important; margin: 0 auto !important; background: radial-gradient(circle at 20% 50%, rgba(120, 119, 198, 0.3), transparent 50%), radial-gradient(circle at 80% 20%, rgba(255, 119, 198, 0.3), transparent 50%), radial-gradient(circle at 40% 80%, rgba(120, 219, 255, 0.3), transparent 50%), var(--primary-gradient) !important; min-height: 100vh !important; padding: 20px !important; position: relative !important; overflow-x: hidden !important; } /* Animated Background Particles */ .gradio-container::before { content: '' !important; position: fixed !important; top: 0 !important; left: 0 !important; width: 100% !important; height: 100% !important; background: url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%23ffffff' fill-opacity='0.05'%3E%3Ccircle cx='7' cy='7' r='1'/%3E%3Ccircle cx='53' cy='7' r='1'/%3E%3Ccircle cx='7' cy='53' r='1'/%3E%3Ccircle cx='53' cy='53' r='1'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E") !important; animation: float 20s ease-in-out infinite !important; pointer-events: none !important; z-index: 0 !important; } /* Main Content Area with Glass Effect */ .main-content { background: var(--glass-bg) !important; backdrop-filter: blur(20px) !important; -webkit-backdrop-filter: blur(20px) !important; border-radius: var(--border-radius-lg) !important; box-shadow: var(--shadow-lg), inset 0 1px 0 var(--glass-border) !important; padding: 40px !important; margin: 20px 0 !important; position: relative !important; z-index: 1 !important; border: 1px solid var(--glass-border) !important; transition: var(--transition) !important; } .main-content:hover { transform: translateY(-2px) !important; box-shadow: 0 25px 50px rgba(0, 0, 0, 0.15), inset 0 1px 0 var(--glass-border) !important; } /* Enhanced Header with Animations */ .markdown h1 { background: var(--primary-gradient) !important; -webkit-background-clip: text !important; -webkit-text-fill-color: transparent !important; background-clip: text !important; font-size: 3rem !important; font-weight: 800 !important; text-align: center !important; margin-bottom: 1.5rem !important; position: relative !important; animation: titleGlow 3s ease-in-out infinite alternate !important; } .markdown h1::after { content: '' !important; position: absolute !important; bottom: -10px !important; left: 50% !important; transform: translateX(-50%) !important; width: 100px !important; height: 4px !important; background: var(--primary-gradient) !important; border-radius: 2px !important; animation: pulse 2s ease-in-out infinite !important; } /* Enhanced Chat Interface */ .chatbot { border: none !important; border-radius: var(--border-radius) !important; box-shadow: var(--shadow-md) !important; background: var(--bg-card) !important; overflow: hidden !important; position: relative !important; } /* Chat Messages with Better Styling */ .chatbot .message-wrap { padding: 20px !important; margin: 15px !important; border-radius: 18px !important; max-width: 85% !important; animation: messageSlideIn 0.5s cubic-bezier(0.4, 0, 0.2, 1) !important; position: relative !important; word-wrap: break-word !important; } .chatbot .message.user { background: var(--primary-gradient) !important; color: white !important; margin-left: auto !important; margin-right: 15px !important; box-shadow: 0 8px 25px rgba(102, 126, 234, 0.4) !important; border-bottom-right-radius: 5px !important; } .chatbot .message.user::before { content: '๐งโ๐ป' !important; position: absolute !important; top: -25px !important; right: 10px !important; font-size: 16px !important; background: var(--bg-card) !important; padding: 5px 8px !important; border-radius: 20px !important; box-shadow: var(--shadow-sm) !important; } .chatbot .message.bot { background: linear-gradient(135deg, #f8f9ff 0%, #e8eeff 100%) !important; color: var(--text-primary) !important; margin-right: auto !important; margin-left: 15px !important; border: 1px solid #e2e8f0 !important; box-shadow: var(--shadow-sm) !important; border-bottom-left-radius: 5px !important; } .chatbot .message.bot::before { content: '๐ค' !important; position: absolute !important; top: -25px !important; left: 10px !important; font-size: 16px !important; background: var(--bg-card) !important; padding: 5px 8px !important; border-radius: 20px !important; box-shadow: var(--shadow-sm) !important; } /* Typing Indicator */ .typing-indicator { display: flex !important; align-items: center !important; padding: 15px 20px !important; margin: 15px !important; background: linear-gradient(135deg, #f8f9ff 0%, #e8eeff 100%) !important; border-radius: 18px !important; max-width: 85% !important; margin-right: auto !important; margin-left: 15px !important; border: 1px solid #e2e8f0 !important; animation: messageSlideIn 0.5s cubic-bezier(0.4, 0, 0.2, 1) !important; } .typing-dots { display: flex !important; align-items: center !important; gap: 4px !important; } .typing-dots span { width: 8px !important; height: 8px !important; border-radius: 50% !important; background: var(--text-light) !important; animation: typingDots 1.4s ease-in-out infinite both !important; } .typing-dots span:nth-child(1) { animation-delay: -0.32s !important; } .typing-dots span:nth-child(2) { animation-delay: -0.16s !important; } .typing-dots span:nth-child(3) { animation-delay: 0s !important; } /* Enhanced Input Areas */ .textbox input, .textbox textarea { border: 2px solid #e2e8f0 !important; border-radius: var(--border-radius) !important; padding: 18px 24px !important; font-size: 16px !important; line-height: 1.5 !important; transition: var(--transition) !important; background: var(--bg-card) !important; box-shadow: var(--shadow-sm) !important; font-family: 'Inter', sans-serif !important; } .textbox input:focus, .textbox textarea:focus { border-color: #667eea !important; box-shadow: 0 0 0 4px rgba(102, 126, 234, 0.1), var(--shadow-md) !important; outline: none !important; transform: translateY(-1px) !important; background: #ffffff !important; } /* Enhanced Buttons with Micro-interactions */ .btn { border-radius: var(--border-radius) !important; font-weight: 600 !important; text-transform: none !important; letter-spacing: 0.3px !important; transition: var(--transition) !important; border: none !important; box-shadow: var(--shadow-sm) !important; padding: 14px 28px !important; font-size: 16px !important; position: relative !important; overflow: hidden !important; cursor: pointer !important; } .btn::before { content: '' !important; position: absolute !important; top: 0 !important; left: -100% !important; width: 100% !important; height: 100% !important; background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent) !important; transition: left 0.5s !important; } .btn:hover::before { left: 100% !important; } .btn-primary { background: var(--primary-gradient) !important; color: white !important; box-shadow: 0 8px 25px rgba(102, 126, 234, 0.3) !important; } .btn-primary:hover { transform: translateY(-3px) scale(1.02) !important; box-shadow: 0 12px 35px rgba(102, 126, 234, 0.4) !important; } .btn-primary:active { transform: translateY(-1px) scale(0.98) !important; } .btn-secondary { background: linear-gradient(135deg, #f7fafc 0%, #edf2f7 100%) !important; color: var(--text-secondary) !important; border: 1px solid #e2e8f0 !important; } .btn-secondary:hover { transform: translateY(-2px) !important; background: linear-gradient(135deg, #edf2f7 0%, #e2e8f0 100%) !important; box-shadow: var(--shadow-md) !important; } /* Enhanced File Upload with Drag & Drop Animation */ .file-upload { border: 3px dashed #cbd5e0 !important; border-radius: var(--border-radius) !important; background: linear-gradient(135deg, #f7fafc 0%, #edf2f7 100%) !important; padding: 40px !important; text-align: center !important; transition: var(--transition) !important; position: relative !important; overflow: hidden !important; cursor: pointer !important; } .file-upload:hover { border-color: #667eea !important; background: linear-gradient(135deg, #edf2f7 0%, #e2e8f0 100%) !important; transform: translateY(-2px) scale(1.01) !important; box-shadow: var(--shadow-md) !important; } .file-upload::before { content: '๐' !important; font-size: 4rem !important; display: block !important; margin-bottom: 15px !important; animation: fileFloat 3s ease-in-out infinite !important; } .file-upload::after { content: 'Drag files here or click to browse' !important; position: absolute !important; bottom: 15px !important; left: 50% !important; transform: translateX(-50%) !important; font-size: 14px !important; color: var(--text-light) !important; font-weight: 500 !important; } /* Enhanced Examples Section */ .examples { background: linear-gradient(135deg, #f8f9ff 0%, #e8eeff 100%) !important; border-radius: var(--border-radius) !important; padding: 30px !important; margin: 25px 0 !important; border: 1px solid #e2e8f0 !important; box-shadow: var(--shadow-sm) !important; position: relative !important; } .examples::before { content: '๐ก' !important; position: absolute !important; top: -15px !important; left: 30px !important; background: var(--bg-card) !important; padding: 10px !important; border-radius: 50% !important; font-size: 20px !important; box-shadow: var(--shadow-sm) !important; } .examples h3 { color: #667eea !important; font-weight: 700 !important; margin-bottom: 20px !important; font-size: 1.3rem !important; margin-left: 20px !important; } /* Feature Cards with Hover Effects */ .feature-card { background: var(--bg-card) !important; border-radius: var(--border-radius) !important; padding: 25px !important; margin: 15px 0 !important; box-shadow: var(--shadow-sm) !important; border-left: 4px solid transparent !important; transition: var(--transition) !important; cursor: pointer !important; } .feature-card:hover { transform: translateY(-5px) translateX(5px) !important; box-shadow: var(--shadow-md) !important; } .feature-card.research { border-left-color: #667eea !important; } .feature-card.code { border-left-color: #48bb78 !important; } .feature-card.data { border-left-color: #ed8936 !important; } .feature-card.image { border-left-color: #dd6b20 !important; } /* Status Indicator with Pulse Animation */ .status-indicator { display: inline-block !important; width: 12px !important; height: 12px !important; border-radius: 50% !important; background: radial-gradient(circle, #48bb78, #38a169) !important; margin-right: 12px !important; animation: statusPulse 2s ease-in-out infinite !important; box-shadow: 0 0 0 0 rgba(72, 187, 120, 0.7) !important; } /* Enhanced Footer */ .footer { background: var(--glass-bg) !important; backdrop-filter: blur(10px) !important; border-radius: var(--border-radius) !important; padding: 30px !important; margin-top: 40px !important; text-align: center !important; border: 1px solid var(--glass-border) !important; box-shadow: var(--shadow-sm) !important; } /* Advanced Animations */ @keyframes titleGlow { 0%, 100% { text-shadow: 0 0 20px rgba(102, 126, 234, 0.5); } 50% { text-shadow: 0 0 30px rgba(102, 126, 234, 0.8), 0 0 40px rgba(118, 75, 162, 0.6); } } @keyframes messageSlideIn { from { opacity: 0; transform: translateY(30px) scale(0.95); } to { opacity: 1; transform: translateY(0) scale(1); } } @keyframes typingDots { 0%, 80%, 100% { transform: scale(0.8); opacity: 0.5; } 40% { transform: scale(1.2); opacity: 1; } } @keyframes fileFloat { 0%, 100% { transform: translateY(0px); } 50% { transform: translateY(-10px); } } @keyframes statusPulse { 0% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(72, 187, 120, 0.7); } 70% { transform: scale(1); box-shadow: 0 0 0 10px rgba(72, 187, 120, 0); } 100% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(72, 187, 120, 0); } } @keyframes float { 0%, 100% { transform: translateY(0px) rotate(0deg); } 33% { transform: translateY(-10px) rotate(1deg); } 66% { transform: translateY(-5px) rotate(-1deg); } } @keyframes pulse { 0%, 100% { transform: scale(1) scaleX(1); } 50% { transform: scale(1.05) scaleX(1.1); } } /* Responsive Design Enhancements */ @media (max-width: 1024px) { .gradio-container { padding: 15px !important; } .main-content { padding: 25px !important; margin: 15px 0 !important; } .markdown h1 { font-size: 2.5rem !important; } } @media (max-width: 768px) { .gradio-container { padding: 10px !important; } .main-content { padding: 20px !important; margin: 10px 0 !important; } .chatbot .message-wrap { max-width: 90% !important; margin: 10px !important; padding: 15px !important; } .markdown h1 { font-size: 2rem !important; } .btn { padding: 12px 20px !important; font-size: 14px !important; } .file-upload { padding: 30px 20px !important; } } @media (max-width: 480px) { .markdown h1 { font-size: 1.8rem !important; } .chatbot .message-wrap { margin: 8px !important; padding: 12px !important; } .main-content { padding: 15px !important; } } /* Custom Scrollbar Enhancement */ ::-webkit-scrollbar { width: 12px; } ::-webkit-scrollbar-track { background: rgba(241, 241, 241, 0.5); border-radius: 6px; } ::-webkit-scrollbar-thumb { background: var(--primary-gradient); border-radius: 6px; border: 2px solid rgba(255, 255, 255, 0.2); } ::-webkit-scrollbar-thumb:hover { background: linear-gradient(135deg, #5a6fd8 0%, #6a4190 100%); } /* Loading States */ .loading { animation: pulse 2s infinite !important; } .processing { position: relative !important; overflow: hidden !important; } .processing::after { content: '' !important; position: absolute !important; top: 0 !important; left: -100% !important; width: 100% !important; height: 100% !important; background: linear-gradient(90deg, transparent, rgba(255,255,255,0.4), transparent) !important; animation: shimmer 2s infinite !important; } @keyframes shimmer { 0% { left: -100%; } 100% { left: 100%; } } /* Dark mode support (future enhancement) */ @media (prefers-color-scheme: dark) { :root { --glass-bg: rgba(26, 32, 44, 0.95); --text-primary: #e2e8f0; --text-secondary: #cbd5e0; --text-light: #a0aec0; --bg-card: #2d3748; --bg-light: #1a202c; } } """ with gr.Blocks(css=custom_css, title="GAIA Lite", theme=gr.themes.Soft()) as demo: missing = missing_secrets() if missing: gr.Markdown( "โ ๏ธ **Missing secrets:** `" + "`, `".join(missing) + "`. Add them in **Space Settings โ Variables and secrets**, then restart. " "Until then the UI loads but questions will fail." ) # Header with enhanced styling with gr.Row(elem_classes="main-content"): gr.Markdown( """
Web search, Wikipedia, academic papers, arXiv research
Python, Bash, SQL, C, Java with real-time results
CSV, Excel, visualizations, statistical analysis
Analysis, OCR, transformations, generation