Spaces:
Sleeping
Sleeping
| """ | |
| app.py - RAG Chatbot with Ultra Premium 3D UI | |
| Run with: python app.py | |
| """ | |
| import os | |
| import sys | |
| import gradio as gr | |
| from dotenv import load_dotenv | |
| load_dotenv() | |
| sys.path.insert(0, os.path.dirname(__file__)) | |
| from src.document_loader import load_document | |
| from src.embeddings import get_embedding_model | |
| from src.vector_database import add_documents_to_store, load_vector_store | |
| from src.rag_pipeline import build_rag_chain, ask_question | |
| from src.utils import save_uploaded_file, split_documents, format_sources | |
| embedding_model = None | |
| rag_chain = None | |
| def _get_embeddings(): | |
| global embedding_model | |
| if embedding_model is None: | |
| embedding_model = get_embedding_model() | |
| return embedding_model | |
| def handle_upload(files) -> str: | |
| global rag_chain | |
| if not files: | |
| return "⚠️ No files uploaded. Please select at least one file." | |
| status_lines = [] | |
| all_chunks = [] | |
| for file in files: | |
| filename = os.path.basename(file.name) | |
| status_lines.append(f"📄 Processing: {filename}") | |
| try: | |
| saved_path = save_uploaded_file(file.name) | |
| documents = load_document(saved_path) | |
| chunks = split_documents(documents) | |
| all_chunks.extend(chunks) | |
| status_lines.append(f" ✅ {filename} — {len(chunks)} chunk(s) extracted") | |
| except ValueError as e: | |
| status_lines.append(f" ❌ {filename} — {e}") | |
| except Exception as e: | |
| status_lines.append(f" ❌ {filename} — Unexpected error: {e}") | |
| if not all_chunks: | |
| return "\n".join(status_lines) + "\n\n⚠️ No text could be extracted." | |
| status_lines.append("\n🔄 Generating embeddings and updating vector store …") | |
| try: | |
| embeddings = _get_embeddings() | |
| vector_store = add_documents_to_store(all_chunks, embeddings) | |
| rag_chain = build_rag_chain(vector_store) | |
| status_lines.append("✅ Vector store updated successfully!") | |
| status_lines.append("💬 You can now ask questions in the Chat tab.") | |
| except EnvironmentError as e: | |
| status_lines.append(f"❌ Configuration error: {e}") | |
| except Exception as e: | |
| status_lines.append(f"❌ Failed to build the RAG pipeline: {e}") | |
| return "\n".join(status_lines) | |
| def handle_question(question: str): | |
| global rag_chain | |
| if not question.strip(): | |
| return "Please type a question first.", "" | |
| if rag_chain is None: | |
| try: | |
| embeddings = _get_embeddings() | |
| vector_store = load_vector_store(embeddings) | |
| if vector_store is None: | |
| return "No documents indexed yet. Please upload documents first.", "" | |
| rag_chain = build_rag_chain(vector_store) | |
| except EnvironmentError as e: | |
| return f"Configuration error: {e}", "" | |
| except Exception as e: | |
| return f"Failed to load the RAG pipeline: {e}", "" | |
| try: | |
| result = ask_question(rag_chain, question) | |
| answer = result["answer"] | |
| sources_text = format_sources(result["sources"]) | |
| return answer, sources_text | |
| except Exception as e: | |
| return f"Error generating answer: {e}", "" | |
| CUSTOM_CSS = """ | |
| @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Space+Grotesk:wght@400;500;600;700;800&display=swap'); | |
| *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } | |
| body, .gradio-container { | |
| background: #030712 !important; | |
| font-family: 'Inter', sans-serif !important; | |
| min-height: 100vh; | |
| overflow-x: hidden; | |
| } | |
| .gradio-container { | |
| max-width: 100% !important; | |
| padding: 0 !important; | |
| margin: 0 !important; | |
| } | |
| /* ── Animated Aurora Background ── */ | |
| body::before { | |
| content: ''; | |
| position: fixed; | |
| top: 0; left: 0; right: 0; bottom: 0; | |
| background: | |
| radial-gradient(ellipse 80% 50% at 20% 20%, rgba(0,212,255,0.07) 0%, transparent 60%), | |
| radial-gradient(ellipse 60% 40% at 80% 10%, rgba(124,58,237,0.08) 0%, transparent 60%), | |
| radial-gradient(ellipse 70% 60% at 50% 90%, rgba(16,185,129,0.05) 0%, transparent 60%); | |
| animation: aurora 12s ease-in-out infinite alternate; | |
| pointer-events: none; | |
| z-index: 0; | |
| } | |
| @keyframes aurora { | |
| 0% { opacity: 1; transform: scale(1) rotate(0deg); } | |
| 50% { opacity: 0.7; transform: scale(1.05) rotate(1deg); } | |
| 100% { opacity: 1; transform: scale(1) rotate(0deg); } | |
| } | |
| /* ── Floating orbs ── */ | |
| body::after { | |
| content: ''; | |
| position: fixed; | |
| width: 500px; height: 500px; | |
| top: -100px; right: -100px; | |
| background: radial-gradient(circle, rgba(0,212,255,0.06) 0%, transparent 70%); | |
| border-radius: 50%; | |
| animation: floatOrb 8s ease-in-out infinite; | |
| pointer-events: none; | |
| z-index: 0; | |
| } | |
| @keyframes floatOrb { | |
| 0%, 100% { transform: translateY(0px) translateX(0px); } | |
| 33% { transform: translateY(30px) translateX(-20px); } | |
| 66% { transform: translateY(-20px) translateX(20px); } | |
| } | |
| /* ── Hero Section ── */ | |
| .hero-wrap { | |
| position: relative; | |
| z-index: 1; | |
| background: linear-gradient(180deg, #050d1a 0%, #030712 100%); | |
| border-bottom: 1px solid rgba(0,212,255,0.1); | |
| padding: 60px 60px 52px; | |
| overflow: hidden; | |
| } | |
| .hero-wrap::before { | |
| content: ''; | |
| position: absolute; | |
| top: 0; left: 0; right: 0; bottom: 0; | |
| 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='%2300d4ff' fill-opacity='0.02'%3E%3Ccircle cx='30' cy='30' r='1'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E"); | |
| pointer-events: none; | |
| } | |
| .hero-grid-line { | |
| position: absolute; | |
| top: 0; left: 0; right: 0; bottom: 0; | |
| background-image: | |
| linear-gradient(rgba(0,212,255,0.03) 1px, transparent 1px), | |
| linear-gradient(90deg, rgba(0,212,255,0.03) 1px, transparent 1px); | |
| background-size: 60px 60px; | |
| pointer-events: none; | |
| } | |
| .hero-chip { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 8px; | |
| background: rgba(0,212,255,0.08); | |
| border: 1px solid rgba(0,212,255,0.2); | |
| border-radius: 100px; | |
| padding: 6px 16px; | |
| font-size: 11px; | |
| font-weight: 600; | |
| letter-spacing: 2px; | |
| text-transform: uppercase; | |
| color: #00d4ff; | |
| font-family: 'Space Grotesk', sans-serif; | |
| margin-bottom: 24px; | |
| } | |
| .hero-chip::before { | |
| content: ''; | |
| width: 6px; height: 6px; | |
| background: #00d4ff; | |
| border-radius: 50%; | |
| box-shadow: 0 0 8px #00d4ff; | |
| animation: pulse 2s ease-in-out infinite; | |
| } | |
| @keyframes pulse { | |
| 0%, 100% { opacity: 1; transform: scale(1); } | |
| 50% { opacity: 0.5; transform: scale(1.3); } | |
| } | |
| .hero-title { | |
| font-family: 'Space Grotesk', sans-serif; | |
| font-size: 56px; | |
| font-weight: 800; | |
| line-height: 1.1; | |
| letter-spacing: -1.5px; | |
| color: #f0f6fc; | |
| margin-bottom: 20px; | |
| } | |
| .hero-title .gradient-text { | |
| background: linear-gradient(135deg, #00d4ff 0%, #7c3aed 50%, #f59e0b 100%); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| background-clip: text; | |
| background-size: 200% 200%; | |
| animation: gradientShift 4s ease infinite; | |
| } | |
| @keyframes gradientShift { | |
| 0% { background-position: 0% 50%; } | |
| 50% { background-position: 100% 50%; } | |
| 100% { background-position: 0% 50%; } | |
| } | |
| .hero-sub { | |
| font-size: 17px; | |
| color: #6e7681; | |
| line-height: 1.7; | |
| max-width: 580px; | |
| margin-bottom: 36px; | |
| font-weight: 400; | |
| } | |
| .hero-tags { | |
| display: flex; | |
| gap: 10px; | |
| flex-wrap: wrap; | |
| } | |
| .hero-tag { | |
| background: rgba(255,255,255,0.04); | |
| border: 1px solid rgba(255,255,255,0.08); | |
| border-radius: 8px; | |
| padding: 8px 16px; | |
| font-size: 13px; | |
| font-weight: 500; | |
| color: #8b949e; | |
| font-family: 'Space Grotesk', sans-serif; | |
| transition: all 0.3s ease; | |
| cursor: default; | |
| transform: perspective(400px) translateZ(0px); | |
| } | |
| .hero-tag:hover { | |
| background: rgba(0,212,255,0.08); | |
| border-color: rgba(0,212,255,0.3); | |
| color: #00d4ff; | |
| transform: perspective(400px) translateZ(8px); | |
| box-shadow: 0 8px 24px rgba(0,212,255,0.15); | |
| } | |
| /* ── Stats Bar ── */ | |
| .stats-bar { | |
| display: grid; | |
| grid-template-columns: repeat(4, 1fr); | |
| gap: 0; | |
| border-bottom: 1px solid rgba(0,212,255,0.08); | |
| position: relative; | |
| z-index: 1; | |
| background: rgba(5,13,26,0.8); | |
| backdrop-filter: blur(20px); | |
| } | |
| .stat-item { | |
| padding: 24px 32px; | |
| border-right: 1px solid rgba(0,212,255,0.08); | |
| transition: background 0.3s ease; | |
| } | |
| .stat-item:last-child { border-right: none; } | |
| .stat-item:hover { background: rgba(0,212,255,0.03); } | |
| .stat-num { | |
| font-family: 'Space Grotesk', sans-serif; | |
| font-size: 32px; | |
| font-weight: 700; | |
| color: #00d4ff; | |
| line-height: 1; | |
| margin-bottom: 4px; | |
| text-shadow: 0 0 20px rgba(0,212,255,0.4); | |
| } | |
| .stat-lbl { | |
| font-size: 11px; | |
| color: #484f58; | |
| font-weight: 500; | |
| letter-spacing: 1px; | |
| text-transform: uppercase; | |
| font-family: 'Space Grotesk', sans-serif; | |
| } | |
| /* ── Main Content ── */ | |
| .main-wrap { | |
| position: relative; | |
| z-index: 1; | |
| padding: 40px 60px 60px; | |
| } | |
| /* ── Tabs ── */ | |
| .tabs-wrap > div:first-child { | |
| background: transparent !important; | |
| border-bottom: 1px solid rgba(255,255,255,0.06) !important; | |
| padding: 0 !important; | |
| margin-bottom: 36px !important; | |
| } | |
| .tabs-wrap > div:first-child button { | |
| background: transparent !important; | |
| border: none !important; | |
| border-bottom: 2px solid transparent !important; | |
| color: #484f58 !important; | |
| font-family: 'Space Grotesk', sans-serif !important; | |
| font-size: 13px !important; | |
| font-weight: 600 !important; | |
| letter-spacing: 0.5px !important; | |
| padding: 14px 24px !important; | |
| margin-bottom: -1px !important; | |
| transition: all 0.25s ease !important; | |
| border-radius: 0 !important; | |
| text-transform: uppercase !important; | |
| } | |
| .tabs-wrap > div:first-child button:hover { | |
| color: #8b949e !important; | |
| background: rgba(255,255,255,0.02) !important; | |
| } | |
| .tabs-wrap > div:first-child button.selected { | |
| color: #00d4ff !important; | |
| border-bottom-color: #00d4ff !important; | |
| text-shadow: 0 0 20px rgba(0,212,255,0.5) !important; | |
| } | |
| /* ── Section headers ── */ | |
| .sec-eyebrow { | |
| font-family: 'Space Grotesk', sans-serif; | |
| font-size: 10px; | |
| font-weight: 700; | |
| letter-spacing: 3px; | |
| text-transform: uppercase; | |
| color: #00d4ff; | |
| margin-bottom: 8px; | |
| } | |
| .sec-title { | |
| font-family: 'Space Grotesk', sans-serif; | |
| font-size: 26px; | |
| font-weight: 700; | |
| color: #e6edf3; | |
| margin-bottom: 6px; | |
| letter-spacing: -0.5px; | |
| } | |
| .sec-desc { | |
| font-size: 14px; | |
| color: #484f58; | |
| line-height: 1.6; | |
| margin-bottom: 28px; | |
| } | |
| /* ── 3D Glass Card ── */ | |
| .glass-card { | |
| background: rgba(13,20,33,0.8); | |
| backdrop-filter: blur(20px); | |
| -webkit-backdrop-filter: blur(20px); | |
| border: 1px solid rgba(0,212,255,0.1); | |
| border-radius: 16px; | |
| padding: 28px; | |
| transform: perspective(1000px) rotateX(0deg) translateZ(0px); | |
| transition: all 0.4s cubic-bezier(0.23, 1, 0.32, 1); | |
| box-shadow: | |
| 0 4px 24px rgba(0,0,0,0.4), | |
| 0 1px 0 rgba(255,255,255,0.05) inset; | |
| margin-bottom: 24px; | |
| } | |
| .glass-card:hover { | |
| transform: perspective(1000px) rotateX(-2deg) translateZ(10px); | |
| border-color: rgba(0,212,255,0.25); | |
| box-shadow: | |
| 0 20px 60px rgba(0,0,0,0.5), | |
| 0 0 40px rgba(0,212,255,0.06), | |
| 0 1px 0 rgba(255,255,255,0.08) inset; | |
| } | |
| /* ── File Upload Zone ── */ | |
| .upload-zone { | |
| border: 2px dashed rgba(0,212,255,0.2) !important; | |
| border-radius: 16px !important; | |
| background: rgba(0,212,255,0.02) !important; | |
| transition: all 0.4s cubic-bezier(0.23, 1, 0.32, 1) !important; | |
| transform: perspective(800px) translateZ(0px); | |
| min-height: 160px !important; | |
| } | |
| .upload-zone:hover { | |
| border-color: rgba(0,212,255,0.5) !important; | |
| background: rgba(0,212,255,0.05) !important; | |
| transform: perspective(800px) translateZ(6px); | |
| box-shadow: 0 16px 40px rgba(0,212,255,0.1), 0 0 0 1px rgba(0,212,255,0.1); | |
| } | |
| /* ── Buttons ── */ | |
| button.primary { | |
| background: linear-gradient(135deg, #0891b2, #00d4ff, #7c3aed) !important; | |
| background-size: 200% 200% !important; | |
| animation: btnGradient 4s ease infinite !important; | |
| border: none !important; | |
| border-radius: 10px !important; | |
| color: #ffffff !important; | |
| font-family: 'Space Grotesk', sans-serif !important; | |
| font-size: 14px !important; | |
| font-weight: 700 !important; | |
| letter-spacing: 0.5px !important; | |
| padding: 14px 32px !important; | |
| text-transform: uppercase !important; | |
| transform: perspective(400px) translateZ(0px); | |
| transition: transform 0.2s ease, box-shadow 0.2s ease !important; | |
| box-shadow: | |
| 0 4px 20px rgba(0,212,255,0.3), | |
| 0 1px 0 rgba(255,255,255,0.2) inset !important; | |
| cursor: pointer !important; | |
| } | |
| @keyframes btnGradient { | |
| 0% { background-position: 0% 50%; } | |
| 50% { background-position: 100% 50%; } | |
| 100% { background-position: 0% 50%; } | |
| } | |
| button.primary:hover { | |
| transform: perspective(400px) translateZ(6px) translateY(-2px) !important; | |
| box-shadow: | |
| 0 12px 40px rgba(0,212,255,0.4), | |
| 0 1px 0 rgba(255,255,255,0.2) inset !important; | |
| } | |
| button.primary:active { | |
| transform: perspective(400px) translateZ(-2px) translateY(1px) !important; | |
| box-shadow: 0 2px 10px rgba(0,212,255,0.2) !important; | |
| } | |
| /* ── Inputs ── */ | |
| textarea, input[type="text"] { | |
| background: rgba(13,20,33,0.9) !important; | |
| border: 1px solid rgba(255,255,255,0.07) !important; | |
| border-radius: 10px !important; | |
| color: #e6edf3 !important; | |
| font-family: 'Inter', sans-serif !important; | |
| font-size: 14px !important; | |
| line-height: 1.6 !important; | |
| padding: 14px 18px !important; | |
| transition: all 0.3s ease !important; | |
| transform: perspective(600px) translateZ(0px); | |
| } | |
| textarea:focus, input[type="text"]:focus { | |
| border-color: rgba(0,212,255,0.4) !important; | |
| outline: none !important; | |
| box-shadow: | |
| 0 0 0 3px rgba(0,212,255,0.08), | |
| 0 8px 24px rgba(0,0,0,0.3) !important; | |
| transform: perspective(600px) translateZ(4px) !important; | |
| } | |
| textarea::placeholder, input::placeholder { | |
| color: #30363d !important; | |
| } | |
| /* ── Labels ── */ | |
| label span, .label-wrap span { | |
| color: #6e7681 !important; | |
| font-size: 11px !important; | |
| font-weight: 600 !important; | |
| letter-spacing: 1.5px !important; | |
| text-transform: uppercase !important; | |
| font-family: 'Space Grotesk', sans-serif !important; | |
| } | |
| /* ── Answer output ── */ | |
| .answer-box textarea { | |
| background: rgba(0,16,32,0.9) !important; | |
| border: 1px solid rgba(0,212,255,0.15) !important; | |
| border-left: 3px solid #00d4ff !important; | |
| border-radius: 12px !important; | |
| color: #cdd9e5 !important; | |
| font-size: 15px !important; | |
| line-height: 1.8 !important; | |
| padding: 20px 24px !important; | |
| box-shadow: 0 0 40px rgba(0,212,255,0.04) inset !important; | |
| } | |
| /* ── Sources output ── */ | |
| .sources-box textarea { | |
| background: rgba(16,0,32,0.9) !important; | |
| border: 1px solid rgba(124,58,237,0.15) !important; | |
| border-left: 3px solid #7c3aed !important; | |
| border-radius: 12px !important; | |
| color: #8b949e !important; | |
| font-size: 12px !important; | |
| font-family: 'Space Grotesk', monospace !important; | |
| line-height: 1.7 !important; | |
| padding: 16px 20px !important; | |
| box-shadow: 0 0 40px rgba(124,58,237,0.04) inset !important; | |
| } | |
| /* ── Status box ── */ | |
| .status-box textarea { | |
| background: rgba(5,13,26,0.95) !important; | |
| border: 1px solid rgba(255,255,255,0.05) !important; | |
| border-radius: 12px !important; | |
| color: #6e7681 !important; | |
| font-size: 12px !important; | |
| font-family: 'Space Grotesk', monospace !important; | |
| line-height: 1.8 !important; | |
| padding: 16px 20px !important; | |
| } | |
| /* ── Help cards ── */ | |
| .hcard { | |
| background: rgba(13,20,33,0.7); | |
| border: 1px solid rgba(255,255,255,0.05); | |
| border-radius: 14px; | |
| padding: 28px; | |
| transform: perspective(800px) translateZ(0px); | |
| transition: all 0.4s cubic-bezier(0.23, 1, 0.32, 1); | |
| box-shadow: 0 4px 20px rgba(0,0,0,0.3); | |
| } | |
| .hcard:hover { | |
| transform: perspective(800px) translateZ(12px) rotateX(-2deg); | |
| border-color: rgba(0,212,255,0.2); | |
| box-shadow: 0 20px 50px rgba(0,0,0,0.4), 0 0 30px rgba(0,212,255,0.05); | |
| } | |
| .hcard-icon { font-size: 28px; margin-bottom: 14px; display: block; } | |
| .hcard h3 { | |
| font-family: 'Space Grotesk', sans-serif; | |
| font-size: 15px; | |
| font-weight: 700; | |
| color: #e6edf3; | |
| margin-bottom: 12px; | |
| letter-spacing: -0.2px; | |
| } | |
| .hcard p, .hcard li { | |
| font-size: 13px; | |
| color: #484f58; | |
| line-height: 1.7; | |
| } | |
| .hcard ol, .hcard ul { padding-left: 18px; margin-top: 8px; } | |
| .hcard li { margin-bottom: 6px; } | |
| .hcard strong { color: #8b949e; } | |
| /* ── Divider ── */ | |
| .div-line { | |
| height: 1px; | |
| background: linear-gradient(90deg, transparent, rgba(0,212,255,0.15), transparent); | |
| margin: 32px 0; | |
| } | |
| /* ── Scrollbar ── */ | |
| ::-webkit-scrollbar { width: 5px; } | |
| ::-webkit-scrollbar-track { background: transparent; } | |
| ::-webkit-scrollbar-thumb { background: rgba(0,212,255,0.2); border-radius: 10px; } | |
| ::-webkit-scrollbar-thumb:hover { background: rgba(0,212,255,0.4); } | |
| /* ── Gradio overrides ── */ | |
| .block, .form { background: transparent !important; border: none !important; padding: 0 !important; } | |
| .gap { gap: 20px !important; } | |
| footer { display: none !important; } | |
| .svelte-1gfkn6j { background: transparent !important; } | |
| """ | |
| def build_interface() -> gr.Blocks: | |
| with gr.Blocks( | |
| title="RAG Document Chatbot", | |
| css=CUSTOM_CSS, | |
| theme=gr.themes.Base( | |
| primary_hue="cyan", | |
| neutral_hue="slate", | |
| font=gr.themes.GoogleFont("Inter"), | |
| ), | |
| ) as demo: | |
| # ── Hero ────────────────────────────────────────────────────────────── | |
| gr.HTML(""" | |
| <div class="hero-wrap"> | |
| <div class="hero-grid-line"></div> | |
| <div class="hero-chip">⚡ AI-Powered · RAG Architecture </div> | |
| <h1 class="hero-title"> | |
| Your Documents.<br> | |
| <span class="gradient-text">Infinite Intelligence.</span> | |
| </h1> | |
| <p class="hero-sub"> | |
| Upload any document and ask questions in plain English. | |
| Get precise, source-backed answers powered by semantic search. | |
| </p> | |
| <div class="hero-tags"> | |
| <span class="hero-tag">📄 PDF</span> | |
| <span class="hero-tag">📝 TXT</span> | |
| <span class="hero-tag">📊 CSV</span> | |
| <span class="hero-tag">📋 DOCX</span> | |
| <span class="hero-tag">🔍 FAISS Vector Search</span> | |
| <span class="hero-tag">🧠 Semantic Retrieval</span> | |
| </div> | |
| </div> | |
| <div class="stats-bar"> | |
| <div class="stat-item"> | |
| <div class="stat-num">4+</div> | |
| <div class="stat-lbl">File Formats</div> | |
| </div> | |
| <div class="stat-item"> | |
| <div class="stat-num">∞</div> | |
| <div class="stat-lbl">Documents</div> | |
| </div> | |
| <div class="stat-item"> | |
| <div class="stat-num">384</div> | |
| <div class="stat-lbl">Embedding Dims</div> | |
| </div> | |
| <div class="stat-item"> | |
| <div class="stat-num">8K</div> | |
| <div class="stat-lbl">Context Window</div> | |
| </div> | |
| </div> | |
| <div class="main-wrap"> | |
| """) | |
| # ── Tabs ────────────────────────────────────────────────────────────── | |
| with gr.Tabs(elem_classes="tabs-wrap"): | |
| # ─ Tab 1: Upload ───────────────────────────────────────────────── | |
| with gr.Tab("📤 Upload"): | |
| gr.HTML(""" | |
| <div class="sec-eyebrow">Step 01 / 02</div> | |
| <div class="sec-title">Upload Your Documents</div> | |
| <div class="sec-desc">Drop your study notes, reports, or any reference files. Supported: PDF · TXT · CSV · DOCX</div> | |
| """) | |
| file_input = gr.File( | |
| label="Drag & drop files here or click to browse", | |
| file_count="multiple", | |
| file_types=[".txt", ".pdf", ".csv", ".docx"], | |
| elem_classes="upload-zone", | |
| ) | |
| upload_btn = gr.Button( | |
| "⚙️ Process & Index Documents", | |
| variant="primary", | |
| size="lg", | |
| ) | |
| gr.HTML('<div class="div-line"></div>') | |
| upload_status = gr.Textbox( | |
| label="Processing Log", | |
| lines=10, | |
| interactive=False, | |
| placeholder="Status messages will appear here …", | |
| elem_classes="status-box", | |
| ) | |
| upload_btn.click(fn=handle_upload, inputs=[file_input], outputs=[upload_status]) | |
| # ─ Tab 2: Chat ─────────────────────────────────────────────────── | |
| with gr.Tab("💬 Ask Questions"): | |
| gr.HTML(""" | |
| <div class="sec-eyebrow">Step 02 / 02</div> | |
| <div class="sec-title">Ask Anything</div> | |
| <div class="sec-desc">Type your question below. The AI retrieves the most relevant passages from your documents and generates a grounded answer.</div> | |
| """) | |
| question_input = gr.Textbox( | |
| label="Your Question", | |
| placeholder="e.g. What is the bias-variance tradeoff and how do you manage it?", | |
| lines=3, | |
| ) | |
| ask_btn = gr.Button("🔍 Get Answer", variant="primary", size="lg") | |
| gr.HTML('<div class="div-line"></div>') | |
| with gr.Row(): | |
| with gr.Column(scale=3): | |
| answer_output = gr.Textbox( | |
| label="Answer", | |
| lines=14, | |
| interactive=False, | |
| placeholder="Your answer will appear here …", | |
| elem_classes="answer-box", | |
| ) | |
| with gr.Column(scale=2): | |
| sources_output = gr.Textbox( | |
| label="Sources Referenced", | |
| lines=14, | |
| interactive=False, | |
| placeholder="Source documents used …", | |
| elem_classes="sources-box", | |
| ) | |
| question_input.submit(fn=handle_question, inputs=[question_input], outputs=[answer_output, sources_output]) | |
| ask_btn.click(fn=handle_question, inputs=[question_input], outputs=[answer_output, sources_output]) | |
| # ─ Tab 3: Help ─────────────────────────────────────────────────── | |
| with gr.Tab("ℹ️ Help"): | |
| gr.HTML(""" | |
| <div class="sec-eyebrow">Documentation</div> | |
| <div class="sec-title">How It Works</div> | |
| <div class="sec-desc">Everything you need to get the best results from your RAG assistant.</div> | |
| <div style="display:grid; grid-template-columns:1fr 1fr; gap:20px; margin-top:8px;"> | |
| <div class="hcard"> | |
| <span class="hcard-icon">📤</span> | |
| <h3>Uploading Documents</h3> | |
| <ol> | |
| <li>Click the <strong>Upload</strong> tab</li> | |
| <li>Drag & drop your files (PDF, TXT, CSV, DOCX)</li> | |
| <li>Click <strong>Process & Index Documents</strong></li> | |
| <li>Wait for the ✅ confirmation</li> | |
| </ol> | |
| </div> | |
| <div class="hcard"> | |
| <span class="hcard-icon">💬</span> | |
| <h3>Asking Questions</h3> | |
| <ol> | |
| <li>Click the <strong>Ask Questions</strong> tab</li> | |
| <li>Type your question in the text box</li> | |
| <li>Press <strong>Enter</strong> or click <strong>Get Answer</strong></li> | |
| <li>Check the Sources panel to verify</li> | |
| </ol> | |
| </div> | |
| <div class="hcard"> | |
| <span class="hcard-icon">💡</span> | |
| <h3>Tips for Better Answers</h3> | |
| <ul> | |
| <li>Be specific in your questions</li> | |
| <li>Upload topic-focused documents</li> | |
| <li>Check the Sources box to verify answers</li> | |
| <li>Rephrase if the first answer is incomplete</li> | |
| </ul> | |
| </div> | |
| <div class="hcard"> | |
| <span class="hcard-icon">⚙️</span> | |
| <h3>Configuration</h3> | |
| <ul> </li> | |
| </ul> | |
| </div> | |
| </div> | |
| """) | |
| gr.HTML("</div>") # close main-wrap | |
| return demo | |
| if __name__ == "__main__": | |
| print("=" * 60) | |
| print(" RAG Document Chatbot — Ultra Premium UI") | |
| print("=" * 60) | |
| print(f" LLM : {os.getenv('LLM_PROVIDER', 'groq')} / {os.getenv('GROQ_MODEL', 'llama-3.1-8b-instant')}") | |
| print(f" URL : http://localhost:7860") | |
| print("=" * 60) | |
| app = build_interface() | |
| app.launch() | |